lots of things, but mainly finish runner. gonna be a ton of debugging now.

This commit is contained in:
Qrakhen 2025-11-09 06:50:14 +01:00
parent 2747c17c25
commit f584435bff
42 changed files with 1156 additions and 545 deletions

View File

@ -2,9 +2,10 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<BaseOutputPath>..\Build\</BaseOutputPath>
</PropertyGroup>
<ItemGroup>

21
Qrakhen.Qamp.CLI/TODO.md Normal file
View File

@ -0,0 +1,21 @@
# 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
### - Finish runner
- add interpretation for all remaining op codes
- make variable operations faster with optimized inline lambda expressions
- better debugging for the instruction set
### CLI / IDE / VSC
- fix 'IDE' in CLI
- create syntax highlighting extension for vs code (if i ever manage to)
### Smaller Goals
- think of a cleaner way to handle Obj (pointers specifically)
- add classic sqript dialect
- fix console "IDE"
- split runtime, digester and core into separate projects

View File

@ -17,7 +17,7 @@ public abstract class Expander<T> :
{
protected T[] Data;
public long Count { get; protected set; }= 0;
public long Count { get; protected set; } = 0;
public long Capacity => Data.Length;
protected Expander(int capacity = 0x10)

View File

@ -2,6 +2,10 @@
namespace Qrakhen.Qamp.Core.Collections;
/// <summary>
/// Basically just a normal array, but implements <see cref="IGetSet{TKey, TValue}"/> to be used with <see cref="Pointer{T}"/>.
/// </summary>
/// <typeparam name="T"></typeparam>
public class FixedArray<T> : IEnumerable<T>, IGetSet<long, T>
{
protected T[] Data;

View File

@ -0,0 +1,46 @@
namespace Qrakhen.Qamp.Core.Collections;
/// <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;
}
public Pointer<T> Branch(long delta = 0)
{
return new Pointer<T>(Target, Ptr + delta);
}
/// <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);
}

View File

@ -2,7 +2,13 @@
namespace Qrakhen.Qamp.Core.Collections;
public class PopStack<T> : IEnumerable<T>, IPop<T>, IPeekable<T>
/// <summary>
/// Pop-Only stack with a fixed array that is gradually reduced.
/// </summary>
public class PopStack<T> :
IEnumerable<T>,
IPop<T>,
IPeekable<T>
{
private readonly T[] _data;

View File

@ -1,8 +1,15 @@
namespace Qrakhen.Qamp.Core.Collections;
public class StackRegister<T> : Expander<T>, IToArray<T>
/// <summary>
/// Push-only Stack that is based on <see cref="Expander{T}"/>.
/// </summary>
/// <typeparam name="T"></typeparam>
public class PushStack<T> :
Expander<T>,
IPush<long, T>,
IToArray<T>
{
public StackRegister(int initialCapacity = 0x10)
public PushStack(int initialCapacity = 0x10)
{
Data = new T[initialCapacity];
}
@ -15,20 +22,22 @@ public class StackRegister<T> : Expander<T>, IToArray<T>
return default;
}
public void Add(T[] array)
public void Push(T[] array)
{
foreach (T value in array) {
Add(value);
}
}
public void Add(IEnumerable<T> array)
public void Push(IEnumerable<T> array)
{
foreach (T value in array) {
Add(value);
}
}
public long Push(T value) => Add(value);
public bool TryGet(long index, out T value)
{
value = default;

View File

@ -1,7 +1,14 @@
namespace Qrakhen.Qamp.Core.Collections;
using System.Collections;
namespace Qrakhen.Qamp.Core.Collections;
/// <summary>
/// Dictionary wrapper implementing <see cref="IGetSet{TKey, TValue}"/>.
/// </summary>
public class Register<TKey, TValue> :
IGetSet<TKey, TValue>
IGetSet<TKey, TValue>,
IToArray<TValue>,
IEnumerable<KeyValuePair<TKey, TValue>>
where TKey : notnull
{
private readonly Dictionary<TKey, TValue> _data = new();
@ -19,7 +26,21 @@ public class Register<TKey, TValue> :
return key;
}
public bool TryGet(TKey key, out TValue? value) => _data.TryGetValue(key, out value);
public TValue Get(TKey key) => _data[key];
public void Set(TKey key, TValue value) => _data[key] = value;
public bool Has(TKey key) => _data.ContainsKey(key);
public TValue[] ToArray() => _data.Values.ToArray();
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
foreach (var item in _data)
yield return item;
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}

View File

@ -1,10 +1,15 @@
namespace Qrakhen.Qamp.Core.Collections;
/// <summary>
/// Stack-like implementation with a few added features like seeking and setting values.
/// </summary>
/// <typeparam name="T"></typeparam>
public class StackLike<T> : Expander<T>,
IPush<long, T>,
IPop<T>,
ISeekable<long, T>,
IPeekable<T>
IPeekable<T>,
IToArray<T>
{
public long Position => Count;
@ -39,4 +44,14 @@ public class StackLike<T> : Expander<T>,
public bool CanPeek(int delta = 0)
=> Count + delta > -1 && Count + delta < Count;
/// <summary>
/// Decimates this stack down to the given position, overwriting everything beyond that when pushing in the future.
/// </summary>
public void Decimate(long position)
{
Count = position;
}
public T[] ToArray() => Data.ToArray();
}

View File

@ -4,10 +4,15 @@ namespace Qrakhen.Qamp.Core.Compilation.Builders;
public class Builder
{
public Builder? Outer;
public Builder? Outer { get; init; }
public FunctionBuilder Function = new();
public StackLike<Local> Locals = [];
public StackLike<Outer> Outers = [];
public int ScopeDepth = 0;
public Builder(Builder? outer = null)
{
Outer = outer;
}
}

View File

@ -0,0 +1,6 @@
namespace Qrakhen.Qamp.Core.Compilation.Builders;
public interface IBuilder<TOut>
{
public TOut Build();
}

View File

@ -4,10 +4,10 @@ using Qrakhen.Qamp.Core.Values;
namespace Qrakhen.Qamp.Core.Compilation.Builders;
public readonly struct SegmentBuilder() : IDebug<string>
public class SegmentBuilder() : IBuilder<Segment>, IDebug<string>
{
public readonly StackRegister<Instruction> Instructions = new();
public readonly StackRegister<Value> Constants = new();
public readonly PushStack<Instruction> Instructions = new();
public readonly PushStack<Value> Constants = new();
public Segment Build()
{

View File

@ -37,6 +37,11 @@ public class DigesterProvider
public object ExpressionDigester; // etc.
}
public abstract class xDigester
{
}
public class Digester : ISteppable<Token>
{
private readonly CompilerState _compiler;
@ -105,7 +110,7 @@ public class Digester : ISteppable<Token>
{
Emit(Op.Return);
Function function = Builder.Function.Build();
Builder = Builder.Outer!;
Builder = Builder.Outer;
return function;
}
@ -114,8 +119,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()
@ -129,15 +134,19 @@ public class Digester : ISteppable<Token>
else if (Match(T.Return)) Return();
else if (Match(T.TypeOf)) TypeOf();
else if (Match(T.Print)) Print();
else if (Match(T.PrintStack)) PrintStack();
else if (Match(T.PrintGlobals)) PrintGlobals();
else if (Match(T.PrintExpr)) PrintExpr();
else if (Match(T.Export)) Export();
else if (Match(T.Import)) Import();
else if (Match(T.ContextOpen)) Context();
else StatementExpression();
else Expression();
}
internal void StatementExpression()
internal void Expression()
{
Expression();
_logger.Method();
ParseExpression();
Consume(T.Semicolon, "Expected ';' after statement");
Emit(Op.Pop); // in an expression statement, we definitely do not want to remain the value on stack,
// as it would fuck over the entire stack for the rest of the execution.
@ -146,7 +155,7 @@ public class Digester : ISteppable<Token>
// a tad bit older me: it's a terrible idea as return a = b; wouldn't work anymore
}
internal void Expression() => WeightedDigest(Weight.Assign);
internal void ParseExpression() => WeightedDigest(Weight.Assign);
internal void Context()
{
@ -221,7 +230,7 @@ public class Digester : ISteppable<Token>
ExpressionParser.Variable(this, name, false);
Consume(T.ContextOpen, "Expected '{' for class body declaration.", false);
while (!Check(T.ContextClose) && !Check(T.Eof))
DeclareMethod();
DeclareMember();
Consume(T.ContextClose, "Expected '}' to end class body declaration.", false);
Emit(Op.Pop);
@ -241,8 +250,9 @@ public class Digester : ISteppable<Token>
DefineVariable(global);
}
internal void DeclareMethod()
internal void DeclareMember()
{
// todo: only methods allowed atm, add fields and shit too
_logger.Method();
Consume(T.Identifier, "Expected method name.", false);
long identifier = IdentifierConstant(Previous.Value!);
@ -258,7 +268,7 @@ public class Digester : ISteppable<Token>
_logger.Method();
long global = ParseVariable();
if (Match(T.Equal))
Expression();
ParseExpression();
else
Emit(Op.Null);
Consume(T.Semicolon, "missing ; after variable declaration");
@ -279,7 +289,7 @@ public class Digester : ISteppable<Token>
{
_logger.Method();
Consume(T.GroupOpen, "Expected '(' after if");
Expression();
ParseExpression();
Consume(T.GroupClose, "Expected ')' after condition");
long then = EmitJump(Op.JumpIfFalse);
@ -308,7 +318,7 @@ public class Digester : ISteppable<Token>
_logger.Method();
long start = Function.CurrentInstruction;
Consume(T.GroupOpen, "Expected while condition to be placed inside parentheses. Missing '('.", false);
Expression();
ParseExpression();
Consume(T.GroupClose, "Expected while condition to be placed inside parentheses. Missing ')'.", false);
long exit = EmitJump(Op.JumpIfFalse);
Emit(Op.Pop);
@ -335,13 +345,13 @@ public class Digester : ISteppable<Token>
} else if (Match(T.Var)) {
DeclareVariable();
} else {
StatementExpression();
Expression();
}
long start = Function.CurrentInstruction;
long exit = -1;
if (!Match(T.Semicolon)) {
Expression();
ParseExpression();
Consume(T.Semicolon, "Expected ';' after condition");
exit = EmitJump(Op.JumpIfFalse);
Emit(Op.Pop);
@ -350,7 +360,7 @@ public class Digester : ISteppable<Token>
if (!Match(T.GroupClose)) {
long body = EmitJump(Op.Jump);
long increment = Function.Segment.Instructions.Count;
Expression();
ParseExpression();
Emit(Op.Pop);
Consume(T.GroupClose, "Expected ')' after for clause");
@ -378,15 +388,36 @@ public class Digester : ISteppable<Token>
internal void Print()
{
_logger.Method();
Expression();
ParseExpression();
Consume(T.Semicolon, "Expected ';' after print call.");
Emit(Op.Print);
}
internal void PrintGlobals()
{
_logger.Method();
Consume(T.Semicolon, "Expected ';' after print call.");
Emit(Op.PrintGlobals);
}
internal void PrintStack()
{
_logger.Method();
Consume(T.Semicolon, "Expected ';' after print call.");
Emit(Op.PrintStack);
}
internal void PrintExpr()
{
_logger.Method();
Consume(T.Semicolon, "Expected ';' after print call.");
Emit(Op.PrintExpr);
}
internal void TypeOf()
{
_logger.Method();
Expression();
ParseExpression();
Emit(Op.Typeof);
}
@ -403,9 +434,9 @@ public class Digester : ISteppable<Token>
while (Builder.Locals.Count > 0 &&
Builder.Locals.Peek().Depth > Builder.ScopeDepth) {
if (Builder.Locals.Peek().IsCaptured) {
Emit(OpCode.CloseUpvalue);
Emit(Op.CloseOuter);
} else {
Emit(OpCode.Pop);
Emit(Op.Pop);
}
Builder.Locals.Pop();
}
@ -425,17 +456,16 @@ public class Digester : ISteppable<Token>
DefineVariable(ParseVariable());
} while (Match(T.Comma));
}
Consume(T.GroupClose, "Expected ')' after argument list.");
Consume(T.ContextOpen, "Expected function body");
Block();
Function function = FinishBuilder();
Emit(Op.Closure);
Emit(MakeConstant(Obj.Create(function)));
Emit(Op.Context);
EmitDynamic(MakeConstant(Obj.Create(function)));
for (int i = 0; i < function.OuterCount; i++) {
Outer outer = next.Outers.Get(i);
Emit(outer.IsLocal ? 1 : 0);
Emit(outer.Index);
EmitDynamic(outer.Index);
}
}
@ -551,12 +581,12 @@ public class Digester : ISteppable<Token>
}
}
internal byte ArgumentList()
internal byte Arguments()
{
byte count = 0;
if (!Check(T.GroupClose)) {
do {
Expression();
ParseExpression();
if (count++ >= 0xFF)
ErrorAtCurrent("How many arguments do you need?");
} while (Match(T.Comma));
@ -621,7 +651,8 @@ public class Digester : ISteppable<Token>
/// </summary>
internal void EmitDynamic(byte[] data)
{
Emit((byte)(data.Length | 0x80));
if (data.Length > 1)
Emit((byte)(data.Length | 0x80)); // 0x80 flag for length marker, anything else is direct value
Emit(data);
}

View File

@ -52,8 +52,8 @@ public static class ExpressionParser
static void Call(Digester digester, bool canAssign)
{
byte count = digester.ArgumentList();
digester.Emit(OpCode.Call, count);
byte count = digester.Arguments();
digester.Emit(OpCode.Invoke, count);
}
static void Dot(Digester digester, bool canAssign)
@ -62,14 +62,14 @@ public static class ExpressionParser
long name = digester.IdentifierConstant(digester.Previous.Value);
if (canAssign && digester.Match(TokenType.Equal)) {
digester.Expression();
digester.EmitDynamic(OpCode.SetProperty, name);
digester.ParseExpression();
digester.EmitDynamic(OpCode.SetMember, name);
} else if (digester.Match(TokenType.GroupOpen)) {
Byte argCount = 0; //argumentList();
digester.Emit(OpCode.Invoke, name.GetBytes());
digester.EmitDynamic(argCount);
byte argCount = digester.Arguments();
digester.EmitDynamic(OpCode.InvokeMember, name);
digester.Emit(argCount);
} else {
digester.EmitDynamic(OpCode.GetProperty, name);
digester.EmitDynamic(OpCode.GetMember, name);
}
}
@ -92,7 +92,7 @@ public static class ExpressionParser
static void Group(Digester digester, bool canAssign)
{
digester.Expression();
digester.ParseExpression();
digester.Consume(TokenType.GroupClose, "Expected ')' after expression.");
}
@ -100,7 +100,7 @@ public static class ExpressionParser
{
int length = 0;
while (!digester.Check(TokenType.ArrayClose)) {
digester.Expression();
digester.ParseExpression();
length++;
if (digester.Check(TokenType.Comma))
digester.Next();
@ -113,19 +113,19 @@ public static class ExpressionParser
static void ArrayAdd(Digester digester, bool canAssign)
{
digester.Expression();
digester.ParseExpression();
digester.Emit(OpCode.ArrayAdd);
}
static void Index(Digester digester, bool canAssign)
{
digester.Expression();
digester.ParseExpression();
digester.Consume(TokenType.ArrayClose, "expected ] after array accessor");
if (canAssign && digester.Match(TokenType.ArrayAdd)) {
digester.Expression();
digester.ParseExpression();
digester.Emit(OpCode.ArrayAdd);
} else if (canAssign && digester.Match(TokenType.Equal)) {
digester.Expression();
digester.ParseExpression();
digester.Emit(OpCode.ArraySet);
} else {
digester.Emit(OpCode.ArrayGet);
@ -183,7 +183,7 @@ public static class ExpressionParser
}
if (canAssign && digester.Match(TokenType.Equal)) {
digester.Expression();
digester.ParseExpression();
digester.Emit(Set);
digester.EmitDynamic(variable);
} else {
@ -211,9 +211,9 @@ public static class ExpressionParser
Variable(digester, "this", false);
if (digester.Match(TokenType.GroupOpen))
{
byte arguments = digester.ArgumentList();
byte arguments = digester.Arguments();
Variable(digester, "base", false);
digester.Emit(OpCode.BaseCall);
digester.Emit(OpCode.InvokeBase);
digester.Emit(arguments);
} else {
Variable(digester, "base", false);
@ -294,7 +294,7 @@ public static class ExpressionParser
_rules[TokenType.Export] = new Rule(null, null, Weight.None);
_rules[TokenType.Import] = new Rule(null, null, Weight.None);
_rules[TokenType.Return] = new Rule(null, null, Weight.None);
_rules[TokenType.Super] = new Rule(Base, null, Weight.None);
_rules[TokenType.Base] = new Rule(Base, null, Weight.None);
_rules[TokenType.This] = new Rule(This, null, Weight.None);
_rules[TokenType.True] = new Rule(Literal, null, Weight.None);
_rules[TokenType.Var] = new Rule(null, null, Weight.None);

View File

@ -1,14 +1,12 @@
using Qrakhen.Qamp.Core.Tokenization;
using Qrakhen.Qamp.Core.Values;
using V = Qrakhen.Qamp.Core.Values.ValueType;
namespace Qrakhen.Qamp.Core;
public class QampException(string message, TokenPosition position = default)
public class QampException(string message, object? context = null)
: Exception(message)
{
public readonly TokenPosition Position = position;
public readonly object? Context = context;
}
public class ReaderException(string message, IReader<Token> reader)
@ -23,14 +21,14 @@ public class TokenException(string message, IReader<Token> reader, Token token)
public readonly Token Token = token;
}
public class ExecutionException(string message, TokenPosition position = default)
: QampException(message, position);
public class RuntimeException(string message, object? context = null)
: QampException(message, context);
public class DivisionByZeroException(string? message, TokenPosition position = default)
: ExecutionException(message ?? "Can not divide by zero", position);
public class DivisionByZeroException(string? message, object? context = null)
: RuntimeException(message ?? "Can not divide by zero", context);
public class ConversionException(string message, Value value, TokenPosition position = default)
: ExecutionException(message, position)
public class ConversionException(string message, Value value, object? context = null)
: RuntimeException(message, context)
{
public readonly Value Value = value;
}

View File

@ -0,0 +1,65 @@
using Qrakhen.Qamp.Core.Collections;
using Qrakhen.Qamp.Core.Values;
using String = Qrakhen.Qamp.Core.Values.Objects.String;
namespace Qrakhen.Qamp.Core.Execution;
public class InstructionPtr : Pointer<Instruction>
{
public Segment Segment;
public Instruction Instruction => Segment.Instructions[Ptr];
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 String NextString()
{
Value value = NextConstant();
if (value.IsString)
return value.Ptr.As<String>()!;
throw new Exception($"Expected string, got {value}");
}
public long NextDynamic()
{
var result = Segment.ReadDynamic(Ptr, out int read);
Ptr += read;
return result;
}
public String? GetStringConstant(long offset)
=> Segment.Constants[offset].Ptr.Value as String;
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;
}
}

View File

@ -2,62 +2,87 @@
public enum OpCode
{
None = 0,
Constant,
Null,
True,
False,
Pop,
Export,
Import,
GetLocal,
SetLocal,
GetGlobal,
DefineGlobal,
SetGlobal,
GetOuter,
SetOuter,
GetProperty,
SetProperty,
Property,
Base,
Equal,
Greater,
Less,
Add,
Subtract,
Multiply,
Divide,
Modulo,
BitwiseAnd,
BitwiseOr,
BitwiseXor,
BitwiseNot,
BitwiseLeft,
BitwiseRight,
Increment,
Decrement,
Not,
Negate,
Ref,
Array,
ArrayGet,
ArraySet,
ArrayAdd,
ArrayRemove,
Print,
PrintExpr,
Typeof,
Jump,
JumpIfFalse,
Loop,
Call,
Invoke,
BaseCall,
Closure,
CloseUpvalue,
Return,
Class,
Inherit,
Method
F_Mask = 0xF0,
None = 0x00,
Constant = 0x01,
Null = 0x02,
Pop = 0x03,
Cast = 0x04,
False = 0x0e,
True = 0x0f,
Val = 0x10,
Ref = 0x11,
SetGlobal = 0x20,
GetGlobal = 0x21,
GetLocal = 0x22,
SetLocal = 0x23,
GetOuter = 0x24,
SetOuter = 0x25,
GetMember = 0x26,
SetMember = 0x27,
DefineGlobal = 0x30,
CloseOuter = 0x31,
AssignValue = 0x40, // unsure
AssignReference = 0x41, // unsure
F_Operation = 0x60,
Add = 0x60,
Subtract = 0x61,
Multiply = 0x62,
Divide = 0x63,
Modulo = 0x64,
BitwiseAnd = 0x65,
BitwiseOr = 0x66,
BitwiseXor = 0x67,
BitwiseLeft = 0x68,
BitwiseRight = 0x69,
F_Unary = 0x0a,
Not = 0x6a,
Negate = 0x6b,
BitwiseNot = 0x6c,
F_Compare = 0x50,
Equal = 0x50,
Greater = 0x51,
Less = 0x52,
Increment = 0x70,
Decrement = 0x71,
Array = 0xc0,
ArrayGet = 0xc1,
ArraySet = 0xc2,
ArrayAdd = 0xc3,
ArrayRemove = 0xc4,
Return = 0x80,
Jump = 0x81,
JumpIfFalse = 0x82,
Loop = 0x83,
Invoke = 0x84,
InvokeBase = 0x85,
InvokeMember = 0x86,
Context = 0x87,
Class = 0xa0,
Member = 0xa1,
Base = 0xa2,
Inherit = 0xa3,
Method = 0xa4,
Static = 0xa5,
Print = 0xe0,
PrintStack = 0xe1,
PrintGlobals = 0xe2,
PrintExpr = 0xe3,
Typeof = 0xef,
Export = 0xfe, // probably shouldnt be an op code but rather be done during digestion
Import = 0xff, // probably shouldnt be an op code but rather be done during digestion
}

View File

@ -1,165 +1,47 @@
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 System.Xml.Linq;
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;
}
}
using T = Values.ValueType;
public class Call
{
public Closure Closure;
public Context Context;
public InstructionPtr Instruction;
public Pointer<Value> StackPtr;
public Call(Closure closure, StackLike<Value> stack, long stackOffset)
public Call(Context context, StackLike<Value> stack, long stackOffset)
{
Closure = closure;
Instruction = new InstructionPtr(Closure.Function.Segment);
Context = context;
Instruction = new InstructionPtr(Context.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;
private 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;
private StackLike<Value> Stack;
private long Cursor => Stack.Position;
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>();
private List<Outer> Outers = [];
private Register<string, Value> Globals = new();
private Register<string, Value> Imports = new();
private Register<string, Value> Exports = new();
public Runner(Options options)
{
@ -172,39 +54,74 @@ public class Runner : IDisposable
{
_logger.Method();
using Reader reader = new Reader(stream);
Digester digester = new Digester(reader);
Compilation.Digester digester = new(reader);
Function function = digester.Digest();
File.WriteAllBytes("func.sqi", function.Segment.Serialize());
Closure closure = new Closure(function);
Context closure = new Context(function);
Push(Obj.Create(closure));
Call(closure, 0);
return Interpret();
Invoke(closure, 0);
return Execute();
}
private ExecutionResult Interpret()
private bool TestFlag(Op code, Op flag) => (code & Op.F_Mask) == flag;
private ExecutionResult Execute()
{
_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;
if (TestFlag(opCode, Op.F_Operation)) {
Value result = ValueOperation.Resolve(PopOperation(opCode));
Push(result);
continue;
}
if (TestFlag(opCode, Op.F_Compare)) {
Value result = ValueOperation.Resolve(PopOperation(opCode));
Push(result);
continue;
}
switch (opCode) {
case Op.Constant: Push(call.Instruction.NextConstant()); break;
case Op.Pop: Pop(); break;
case Op.True: Push(Value.True); break;
case Op.False: Push(Value.False); break;
case Op.GetGlobal: {
string? name = call.Instruction.GetStringConstant(call.Instruction.NextDynamic())?.Value;
if (string.IsNullOrEmpty(name))
throw new RuntimeException($"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.SetGlobal: {
string? name = call.Instruction.GetStringConstant(call.Instruction.NextDynamic())?.Value;
if (string.IsNullOrEmpty(name))
throw new RuntimeException($"tried to set global variable with empty name");
if (!Globals.Has(name))
throw new RuntimeException($"tried to set a value of non-existing global variable");
Globals[name] = Pop();
_logger.Verbose($"set global {name} = {Globals[name]}");
break;
}
case Op.DefineGlobal: {
string? name = call.Instruction.GetStringConstant(call.Instruction.NextDynamic())?.Value;
if (string.IsNullOrEmpty(name))
throw new RuntimeException($"tried to define global variable with empty name");
Globals[name] = Pop();
_logger.Verbose($"defined global {name} as {Globals[name]}");
break;
}
case Op.GetLocal: {
long slot = call.Instruction.NextDynamic();
@ -220,38 +137,158 @@ public class Runner : IDisposable
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]}");
case Op.GetOuter: {
long slot = call.Instruction.NextDynamic();
Push(call.Context.Outers[slot].Value);
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]}");
case Op.SetOuter: {
long slot = call.Instruction.NextDynamic();
call.Context.Outers[slot].Value = Peek();
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()})");
case Op.GetMember: {
Value value = Peek();
if (!value.Is(T.Instance))
return Error("Only instances have members.");
Instance instance = value.Ptr.As<Instance>()!;
string name = call.Instruction.NextString().Value!;
if (instance.Values.TryGet(name, out Value member)) {
Pop(); // remove instance from stack
Push(member);
} else if (!BindMethod(instance.Class, name)) {
return Error($"Could not bind member {name} to instance {instance} of class {instance.Class.Name}!");
}
break;
}
case Op.SetMember: {
Value value = Peek(-2);
if (!value.Is(T.Instance))
return Error("Only instances have members.");
Instance instance = value.Ptr.As<Instance>()!;
string name = call.Instruction.NextString().Value!;
instance.Values.Set(name, Peek());
Value move = Pop();
Pop(); // remove instance from stack
Push(move);
break;
}
case Op.Base: {
string name = call.Instruction.NextString().Value!;
Class @class = Pop().Ptr.As<Class>()!;
if (!BindMethod(@class, name))
return Error($"Could not bind member {name} to base class {@class.Name}");
break;
}
case Op.Jump: {
long delta = call.Instruction.NextLong();
call.Instruction.Ptr += delta;
break;
}
case Op.JumpIfFalse: {
long delta = call.Instruction.NextLong();
if (Peek(0).IsFalsy)
call.Instruction.Ptr += delta;
break;
}
case Op.Loop: {
long delta = call.Instruction.NextLong();
call.Instruction.Ptr -= delta;
break;
}
case Op.Invoke: {
byte count = call.Instruction.Next();
if (!Invoke(Peek(-(count + 1)), count))
return Error($"Could not invoke function.");
call = Calls.Peek();
break;
}
case Op.InvokeMember: {
string name = call.Instruction.NextString().Value!;
byte argumentCount = call.Instruction.Next();
if (!Invoke(name, argumentCount))
return Error($"Could not invoke member {name}.");
call = Calls.Peek();
break;
}
case Op.InvokeBase: {
string name = call.Instruction.NextString().Value!;
byte argumentCount = call.Instruction.Next();
Class @class = Pop().Ptr.As<Class>()!;
if (!Invoke(@class, name, argumentCount))
return Error($"Could not invoke member {name} of base class {@class.Name}!");
call = Calls.Peek();
break;
}
case Op.Context: {
Function function = call.Instruction.NextConstant().Ptr.As<Function>()!;
Context context = new Context(function);
Push(Obj.Create(context));
for (int i = 0; i < context.Function.OuterCount; i++) {
Outer outer = context.Outers[i];
bool isLocal = call.Instruction.Next() == 1;
long index = call.Instruction.NextDynamic();
if (isLocal)
context.Outers.Add(CaptureOuter(call.StackPtr.Branch(index)));
else
context.Outers.Add(call.Context.Outers[index]);
}
break;
}
case Op.CloseOuter: {
CloseOuters(new Pointer<Value>(Stack, Cursor));
Pop();
break;
}
case Op.Return: // todo: not done
Value result = Pop();
CloseOuters(call.StackPtr.Branch());
Calls.Pop();
if (Calls.Count == 0) {
Pop();
return ExecutionResult.OK;
}
Stack.Decimate(call.StackPtr.Ptr);
Push(result);
call = Calls.Peek();
break;
case Op.Class: {
Push(Obj.Create(new Class(call.Instruction.NextString().Value!)));
break;
}
case Op.Inherit: {
Value @class = Peek(-2);
if (!@class.Is(T.Class))
return Error($"Can not inherit from {@class}, value must be of type Class.");
Class baseClass = @class.Ptr.As<Class>()!;
Class inheritingClass = Peek().Ptr.As<Class>()!;
foreach (var member in baseClass.Members) {
if (!inheritingClass.Members.Has(member.Key))
inheritingClass.Members.Add(member.Key, member.Value);
}
Pop(); //
break;
}
case Op.Method:
DefineMember(call.Instruction.NextString().Value!);
break;
case Op.Typeof: {
Value value = Pop();
@ -265,17 +302,22 @@ public class Runner : IDisposable
break;
}
case Op.Return: // todo: not done
Value result = Pop();
Calls.Pop();
if (Calls.Count == 0) {
Pop();
return ExecutionResult.OK;
case Op.PrintStack: {
for (int i = 0; i < Cursor; i++) {
Console.WriteLine($"{Stack[i].ToString(true)}");
}
Cursor = call.StackPtr.Ptr; // well find a better way...
Push(result);
call = Calls.Peek();
break;
}
case Op.PrintGlobals: {
foreach (var value in Globals) {
Console.WriteLine($"{value.Key}: {value.Value.ToString(true)}");
}
break;
}
default:
return Error($"Unexpected or not yet supported OpCode {opCode}!");
}
} while (call.Instruction.Segment.Instructions.Length > call.Instruction.Ptr);
@ -283,7 +325,34 @@ public class Runner : IDisposable
return ExecutionResult.OK;
}
private bool Call(Closure closure, int argumentCount)
private Outer CaptureOuter(Pointer<Value> target)
{
Outer? outer = null;
for (int i = Outers.Count; i-->0;) {
outer = Outers[i];
if (outer.Target.Ptr <= target.Ptr)
break;
}
if (outer != null && outer.Target.Ptr == target.Ptr)
return outer;
return new Outer(target);
}
private void CloseOuters(Pointer<Value> last)
{
for (int i = Outers.Count;i-->0;) {
Outer outer = Outers[i];
if (outer.IsClosed)
continue;
if (outer.Target.Ptr < last.Ptr)
return;
outer.Close();
}
}
private bool Invoke(Context closure, int argumentCount)
{
_logger.Method();
if (argumentCount != closure.Function.ArgumentCount)
@ -297,6 +366,90 @@ public class Runner : IDisposable
return true;
}
private bool Invoke(string methodName, int argumentCount)
{
Value value = Peek(-argumentCount);
if (!value.Is(T.Instance)) {
Error($"Can not call {methodName} on value of {value}");
return false;
}
Instance instance = value.Ptr.As<Instance>()!;
if (instance.Values.TryGet(methodName, out Value method))
return Invoke(method, argumentCount);
return Invoke(instance.Class, methodName, argumentCount);
}
private bool Invoke(Class @class, string methodName, int argumentCount)
{
if (@class.Members.TryGet(methodName, out Value method))
return Invoke(method, argumentCount);
Error($"Could not call method {methodName} on class {@class.Name}: Member does not exist");
return false;
}
private bool Invoke(Value value, int argumentCount)
{
if (!value.IsObj) {
Error($"Can only call values of object type! Tried to call {value.ToString(true)}", value);
return false;
}
if (value.Is(T.Method)) {
Method method = value.Ptr.As<Method>()!;
Stack.Set(Cursor - method.Function.ArgumentCount - 1, method.Receiver);
return Invoke(method, method.Function.ArgumentCount);
} else if (value.Is(T.Context)) {
Context? context = value.Ptr.As<Context>()!;
return Invoke(context, context.Function.ArgumentCount);
} else if (value.Is(T.Native)) {
throw new NotImplementedException("i dont really want globals in this language so lets see");
} else if (value.Is(T.Class)) {
Class? @class = value.Ptr.As<Class>()!;
Stack.Set(Cursor - argumentCount - 1, Instantiate(@class));
if (@class.Members.TryGet(@class.Name, out Value ctor)) {
Context? context = ctor.Ptr.As<Context>()!;
return Invoke(context, argumentCount);
} else if (argumentCount != 0) {
Error($"{@class.Name}() expects 0 arguments but received {argumentCount}");
return false;
}
} else {
return false;
}
return true;
}
private bool BindMethod(Class @class, string name)
{
if (!@class.Members.TryGet(name, out Value value)) {
Error($"Undefined member '{name}' on class {@class.Name}");
return false;
}
Method method = new Method(value.Ptr.As<Function>()!, Peek());
Pop(); // remove instance from stack
Push(Obj.Create(method));
return true;
}
private Value Instantiate(Class @class)
{
Instance instance = new Instance(@class);
// todo: init fields etc here too
return Obj.Create(instance);
}
private void DefineMember(string name)
{
Value method = Peek();
Class @class = Peek(-2).Ptr.As<Class>()!;
@class.Members.Set(name, method);
Pop();
}
private Value Peek(int delta = -1)
{
_logger.Method();
@ -315,92 +468,23 @@ public class Runner : IDisposable
Stack.Push(value);
}
private void OpNot()
{
Value value = Pop();
Push(ValueOperation.Not(value));
}
private void OpAdd()
private Operation PopOperation(Op code)
{
_logger.Method(code);
if ((code & Op.F_Unary) == Op.F_Unary)
return new Operation(code, Pop(), default);
Value right = Pop();
Value left = Pop();
Push(ValueOperation.Add(left, right));
return new Operation(code, left, right);
}
private void OpSubtract()
private ExecutionResult Error(string message, object? context = null, bool @throw = true)
{
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));
_logger.Method(message);
_logger.Error(message, context);
if (@throw)
throw new QampException(message, context);
return ExecutionResult.Execution;
}
public void Dispose()
@ -409,6 +493,11 @@ public class Runner : IDisposable
}
}
public class GarbageCollector
{
}
public enum ExecutionResult
{
OK = 0x0000,

View File

@ -29,18 +29,27 @@ public class Segment(
}
/// <summary>
/// Although this always returns a long, the actually stored data in the instruction set has a dynamic width (anything between 1 and 8 bytes)
/// Although this always returns a long, the actually stored data in the instruction set has a dynamic width
/// (anything between 1 and 9 bytes, 1 if it's just a byte, and 1 + n, where n is up to 8 bytes).<br/>
/// If the first byte read is masked with 0x80, it means that this byte represents the amount of bytes that
/// shall be read. If it is not, the byte is returned directly (for smaller offsets).
/// </summary>
/// <param name="offset"></param>
/// <returns></returns>
public long ReadDynamic(long offset, out byte length)
/// <remarks>
/// All of this is done to compress compiled instruction size.
/// Todo: It will need to be tested wheter the cost on performance is stronger than anticipated.
/// </remarks>
public long ReadDynamic(long offset, out int read)
{
length = (byte)(Read(offset) ^ 0x80);
byte[] bytes = new byte[8];
for (int i = 0; i < length; i++) {
bytes[i] = Instructions[offset + 1 + i];
}
return bytes.ToInt64();
byte value = Read(offset);
read = 1;
if (value < 0x80) // 0x80 flag for length, less than 0x80 means direct read
return value;
int length = value ^ 0x80;
read += length;
var data = new byte[8];
for (int i = 0; i < length; i++)
data[i] = Read(offset + i);
return data.ToInt64();
}
public short ReadShort(long offset) => BitConverter.ToInt16(Read(offset, 2));

View File

@ -0,0 +1,197 @@
using Qrakhen.Qamp.Core.Collections;
using Qrakhen.Qamp.Core.Compilation;
using Qrakhen.Qamp.Core.Logging;
using Qrakhen.Qamp.Core.Values;
namespace Qrakhen.Qamp.Core.Execution;
public readonly record struct Operation(OpCode OpCode, Value Left, Value Right);
public delegate Value OperationHandler(Operation operation);
public static class ValueOperation
{
private static readonly ILogger _logger = LoggerService.Get(nameof(ValueOperation));
private static readonly Register<OpCode, OperationHandler> _operations;
public static Value Resolve(Operation operation)
{
_logger.Method($"{operation.Left} {operation.OpCode} {operation.Right}");
return _operations[operation.OpCode].Invoke(operation);
}
static ValueOperation()
{
_operations = new Register<OpCode, OperationHandler>();
_operations.Add(OpCode.Equal, Equal);
_operations.Add(OpCode.Greater, Greater);
_operations.Add(OpCode.Less, Less);
_operations.Add(OpCode.Not, Not);
_operations.Add(OpCode.Add, Add);
_operations.Add(OpCode.Subtract, Subtract);
_operations.Add(OpCode.Divide, Divide);
_operations.Add(OpCode.Modulo, Modulo);
_operations.Add(OpCode.Multiply, Multiply);
_operations.Add(OpCode.Negate, Negate);
_operations.Add(OpCode.BitwiseOr, BitwiseOr);
_operations.Add(OpCode.BitwiseAnd, BitwiseAnd);
_operations.Add(OpCode.BitwiseLeft, BitwiseLeft);
_operations.Add(OpCode.BitwiseRight, BitwiseRight);
_operations.Add(OpCode.BitwiseNot, BitwiseInvert);
_operations.Add(OpCode.BitwiseXor, BitwiseXor);
}
public static Value Equal(Operation operation)
{
Value a = operation.Left;
Value b = operation.Right;
Assert.NotVoid(a, b);
Assert.IsPrimitive(a, b);
return new Value(a.Dynamic == b.Dynamic);
}
public static Value Greater(Operation operation)
{
Value a = operation.Left;
Value b = operation.Right;
Assert.NotVoid(a, b);
Assert.IsPrimitive(a, b);
return new Value(a.Dynamic > b.Dynamic);
}
public static Value Less(Operation operation)
{
Value a = operation.Left;
Value b = operation.Right;
Assert.NotVoid(a, b);
Assert.IsPrimitive(a, b);
return new Value(a.Dynamic < b.Dynamic);
}
public static Value Not(Operation operation)
{
Value value = operation.Left;
_logger.Method($"{value}");
Assert.NotVoid(value);
ulong inner = value.Unsigned;
return new Value(inner == 0);
}
public static Value Add(Operation operation)
{
Value a = operation.Left;
Value b = operation.Right;
Assert.NotVoid(a, b);
if (a.IsString)
return Values.Objects.String.Make($"{a}{b}");
Assert.IsPrimitive(a, b);
bool convert = a.Type != b.Type;
return new Value(a.Dynamic + b.Dynamic);
}
public static Value Subtract(Operation operation)
{
Value a = operation.Left;
Value b = operation.Right;
Assert.NotVoid(a, b);
Assert.IsPrimitive(a, b);
return new Value(a.Dynamic - b.Dynamic);
}
public static Value Divide(Operation operation)
{
Value a = operation.Left;
Value b = operation.Right;
Assert.NotVoid(a, b);
Assert.IsPrimitive(a, b);
Assert.NotZero(b);
return new Value(a.Dynamic / b.Dynamic);
}
public static Value Modulo(Operation operation)
{
Value a = operation.Left;
Value b = operation.Right;
Assert.NotVoid(a, b);
Assert.IsPrimitive(a, b);
Assert.NotZero(b);
return new Value(a.Dynamic % b.Dynamic);
}
public static Value Multiply(Operation operation)
{
Value a = operation.Left;
Value b = operation.Right;
Assert.NotVoid(a, b);
Assert.IsPrimitive(a, b);
return new Value(a.Dynamic * b.Dynamic);
}
public static Value Negate(Operation operation)
{
Value value = operation.Left;
Assert.NotVoid(value);
Assert.IsPrimitive(value);
return new Value(-value.Dynamic);
}
public static Value BitwiseOr(Operation operation)
{
Value a = operation.Left;
Value b = operation.Right;
Assert.NotVoid(a, b);
Assert.IsPrimitive(a, b);
Assert.IsInteger(a, b);
return new Value(a.Dynamic | b.Dynamic);
}
public static Value BitwiseAnd(Operation operation)
{
Value a = operation.Left;
Value b = operation.Right;
Assert.NotVoid(a, b);
Assert.IsPrimitive(a, b);
Assert.IsInteger(a, b);
return new Value(a.Dynamic & b.Dynamic);
}
public static Value BitwiseXor(Operation operation)
{
Value a = operation.Left;
Value b = operation.Right;
Assert.NotVoid(a, b);
Assert.IsPrimitive(a, b);
Assert.IsInteger(a, b);
return new Value(a.Dynamic ^ b.Dynamic);
}
public static Value BitwiseLeft(Operation operation)
{
Value a = operation.Left;
Value b = operation.Right;
Assert.NotVoid(a, b);
Assert.IsPrimitive(a, b);
Assert.IsInteger(a, b);
return new Value(a.Dynamic << (int)(b.Dynamic ?? 0));
}
public static Value BitwiseRight(Operation operation)
{
Value a = operation.Left;
Value b = operation.Right;
Assert.NotVoid(a, b);
Assert.IsPrimitive(a, b);
Assert.IsInteger(a, b);
return new Value(a.Dynamic >> (int)(b.Dynamic ?? 0));
}
public static Value BitwiseInvert(Operation operation)
{
Value a = operation.Left;
Assert.NotVoid(a);
Assert.IsPrimitive(a);
Assert.IsInteger(a);
return new Value(~a.Dynamic);
}
}

View File

@ -0,0 +1,19 @@
namespace Qrakhen.Qamp.Core.Injector;
/// <summary>
/// Class used to export custom .dll implementations as binary instruction files (*.sqi).
/// </summary>
public class Injector
{
}
public enum Scope
{
/// <summary>Within the global scope of the file that is importing this library</summary>
Global,
/// <summary>Within the scope of the module this library exports</summary>
Module,
/// <summary>An extension of the built-in <see cref="Values.Value"/> type</summary>
Extension
}

View File

@ -5,5 +5,6 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<BaseOutputPath>..\Build\</BaseOutputPath>
</PropertyGroup>
</Project>

View File

@ -88,7 +88,7 @@ public class DefaultDialect : Dialect
Define(Ref, "ref");
Define(Function, "function");
Define(Class, "class");
Define(Super, "base");
Define(Base, "base");
Define(TypeOf, "typeof");
Define(Print, "print");
Define(Import, "import");
@ -109,7 +109,7 @@ public class ClassicDialect : DefaultDialect
Define(Return, "<:");
Define(Ref, "*&");
Define(Function, "fq");
Define(Super, "^~");
Define(Base, "^~");
Define(TypeOf, "?:");
Define(Print, "::");
Define(Import, "<!");

View File

@ -32,13 +32,19 @@ internal static partial class ReaderPatterns
Keywords["var"] = Var;
Keywords["while"] = While;
Keywords["do"] = Do;
Keywords["typeof"] = TypeOf;
Keywords["ref"] = Ref;
Keywords["function"] = Function;
Keywords["funqtion"] = Function;
Keywords["fq"] = Function;
Keywords["funq"] = Function;
Keywords["return"] = Return;
Keywords["class"] = Class;
Keywords["super"] = Super;
Keywords["base"] = Base;
Keywords["typeof"] = TypeOf;
Keywords["print"] = Print;
Keywords["globals"] = PrintGlobals;
Keywords["stack"] = PrintStack;
Keywords["expr"] = PrintExpr;
Keywords["import"] = Import;
Keywords["export"] = Export;
}

View File

@ -87,10 +87,13 @@ public enum TokenType
Ref,
Function,
Class,
Super,
Base,
TypeOf,
Print,
PrintStack,
PrintGlobals,
PrintExpr,
TypeOf,
Import,
Export

View File

@ -0,0 +1,7 @@
namespace Qrakhen.Qamp.Core.Values.Objects;
public class Class(string name) : Obj(ValueType.Class)
{
public readonly string Name = name;
public Table Members = new();
}

View File

@ -1,12 +1,11 @@
using Qrakhen.Qamp.Core.Collections;
using Qrakhen.Qamp.Core.Execution;
namespace Qrakhen.Qamp.Core.Values.Objects;
public class Closure(Function function) : Obj(ValueType.Context)
public class Context(Function function, ValueType type = ValueType.Context) : Obj(type)
{
public Function Function = function;
public StackRegister<OuterWrapper> PreValues = new();
public PushStack<Outer> Outers = new();
public override string ToString() => "{}";
}

View File

@ -9,5 +9,5 @@ public class Function(string name, Segment segment, int outerCount, int argument
public readonly int OuterCount = outerCount;
public readonly int ArgumentCount = argumentCount;
public override string ToString() => $"{name}()";
public override string ToString() => $"{Name}()";
}

View File

@ -0,0 +1,7 @@
namespace Qrakhen.Qamp.Core.Values.Objects;
public class Instance(Class @class) : Obj(ValueType.Instance)
{
public readonly Class Class = @class;
public Table Values = new();
}

View File

@ -0,0 +1,6 @@
namespace Qrakhen.Qamp.Core.Values.Objects;
public class Method(Function function, Value receiver) : Context(function, ValueType.Method)
{
public Value Receiver = receiver;
}

View File

@ -0,0 +1,40 @@
using Qrakhen.Qamp.Core.Collections;
namespace Qrakhen.Qamp.Core.Values.Objects;
using T = ValueType;
/// <summary>
/// Wrapper to access values that are spread around the stack inside contexts
/// </summary>
public class Outer(Pointer<Value> target) : Obj(T.Outer)
{
private Value _stored = Value.Void;
public Pointer<Value> Target = target;
public bool IsClosed { get; private set; }
/// <summary>
/// Dynamic getter & setter that returns or writes to either the target value directly,
/// or the stored value if this <see cref="Outer"/> has been closed.
/// </summary>
public Value Value
{
get => IsClosed ? _stored : Target!.Get();
set {
if (IsClosed)
_stored = value;
else
Target.Set(value);
}
}
public Value Close()
{
if (IsClosed)
throw new Exception($"meh, something not right here (cant close already closed outer)");
_stored = Target!.Get();
IsClosed = true;
return _stored;
}
}

View File

@ -0,0 +1,8 @@
using Qrakhen.Qamp.Core.Collections;
namespace Qrakhen.Qamp.Core.Values.Objects;
public class Table : Register<string, Value>
{
}

View File

@ -1,13 +1,14 @@
using Qrakhen.Qamp.Core.Values.Objects;
using System.Runtime.InteropServices;
using Qrakhen.Qamp.Core.Collections;
using System.Diagnostics.CodeAnalysis;
namespace Qrakhen.Qamp.Core.Values;
[StructLayout(LayoutKind.Sequential, Size = sizeof(ulong))]
public readonly struct Ptr
{
private static readonly StackRegister<Obj> _register = new(0x10);
private static readonly PushStack<Obj> _register = new(0x10);
[Serialized] public readonly Address Address;
@ -31,5 +32,13 @@ public readonly struct Ptr
Address = _register.Add(obj);
}
public T? As<T>(bool throwWhenNull = true) where T : Obj
{
T? value = Value as T;
if (value == null && throwWhenNull)
throw new RuntimeException($"Could not convert Object {Value} to target type {typeof(T)}.", Value);
return value;
}
public override string ToString() => $"0x{Value}";
}

View File

@ -15,6 +15,8 @@ public interface IValue
public readonly struct Value : IValue, IDebug<string>
{
public static readonly Value Void = new Value();
public static readonly Value True = new Value(true);
public static readonly Value False = new Value(false);
[FieldOffset(0x00)] [Serialized] public readonly ulong Unsigned;
[FieldOffset(0x00)] [Serialized] public readonly long Signed;
@ -29,8 +31,6 @@ public readonly struct Value : IValue, IDebug<string>
[FieldOffset(0x0c)] [Serialized] public readonly uint Meta; // like lengths of arrays or lists
public T ValueType => Type;
private Value(T type) => Type = type;
public Value() : this(T.Void) { }
@ -45,6 +45,19 @@ public readonly struct Value : IValue, IDebug<string>
public dynamic? Dynamic => AsDynamic();
public T ValueType => Type;
public bool IsNumber => IsSigned || IsUnsigned || IsDecimal;
public bool IsSigned => Is(T.Signed);
public bool IsUnsigned => Is(T.Unsigned);
public bool IsDecimal => Is(T.Decimal);
public bool IsBool => Is(T.Bool);
public bool IsString => Is(T.String);
public bool IsRef => Is(T.Reference, false);
public bool IsObj => Is(T.Object, false);
public bool IsFalsy => IsBool ? Bool == false : Unsigned == 0;
public bool Is(T type, bool exact = true)
{
return exact ? (Type & type) == type : (Type & type) > 0;
@ -52,7 +65,10 @@ public readonly struct Value : IValue, IDebug<string>
private unsafe dynamic? AsDynamic(bool throwWhenNull = true)
{
if (Type.HasFlag(T.Object))
if (IsString)
return Ptr.As<String>()!.Value;
if (IsObj)
return Ptr.Value;
return Type switch {
@ -88,13 +104,18 @@ public readonly struct Value : IValue, IDebug<string>
public override int GetHashCode() => Unsigned.GetHashCode();
public override unsafe string ToString()
public override string ToString() => ToString(false);
public string ToString(bool includeType)
{
if (!includeType)
return $"{AsDynamic(false)}";
string type = Type.ToString();
if (Type.HasFlag(T.Pointer))
type = Ptr.Value?.Type.ToString() ?? "NullPtr";
return $"[{type}, {AsDynamic(false) ?? "null"}]";
return $"<{type}, {AsDynamic(false) ?? "null"}>";
}
public string Debug(DebugLevel level = DebugLevel.None)

View File

@ -1,121 +0,0 @@
using Qrakhen.Qamp.Core.Logging;
namespace Qrakhen.Qamp.Core.Values;
public static class ValueOperation
{
private static ILogger _logger = LoggerService.Get(nameof(ValueOperation));
public static Value Not(Value v)
{
_logger.Method($"{v}");
Assert.NotVoid(v);
ulong inner = v.Unsigned;
return new Value(inner == 0);
}
public static Value Add(Value a, Value b)
{
_logger.Method($"{a}, {b}");
Assert.NotVoid(a, b);
Assert.IsPrimitive(a, b);
bool convert = a.Type != b.Type; // meh, lets make c# do that for us for now
return new Value(a.Dynamic + b.Dynamic);
}
public static Value Subtract(Value a, Value b)
{
_logger.Method($"{a}, {b}");
Assert.NotVoid(a, b);
Assert.IsPrimitive(a, b);
return new Value(a.Dynamic - b.Dynamic);
}
public static Value Divide(Value a, Value b)
{
_logger.Method($"{a}, {b}");
Assert.NotVoid(a, b);
Assert.IsPrimitive(a, b);
Assert.NotZero(b);
return new Value(a.Dynamic / b.Dynamic);
}
public static Value Modulo(Value a, Value b)
{
_logger.Method($"{a}, {b}");
Assert.NotVoid(a, b);
Assert.IsPrimitive(a, b);
Assert.NotZero(b);
return new Value(a.Dynamic % b.Dynamic);
}
public static Value Multiply(Value a, Value b)
{
_logger.Method($"{a}, {b}");
Assert.NotVoid(a, b);
Assert.IsPrimitive(a, b);
return new Value(a.Dynamic * b.Dynamic);
}
public static Value Negate(Value a)
{
_logger.Method($"{a}");
Assert.NotVoid(a);
Assert.IsPrimitive(a);
return new Value(-a.Dynamic);
}
public static Value BitwiseOr(Value a, Value b)
{
_logger.Method($"{a}, {b}");
Assert.NotVoid(a, b);
Assert.IsPrimitive(a, b);
Assert.IsInteger(a, b);
return new Value(a.Dynamic | b.Dynamic);
}
public static Value BitwiseAnd(Value a, Value b)
{
_logger.Method($"{a}, {b}");
Assert.NotVoid(a, b);
Assert.IsPrimitive(a, b);
Assert.IsInteger(a, b);
return new Value(a.Dynamic & b.Dynamic);
}
public static Value BitwiseXor(Value a, Value b)
{
_logger.Method($"{a}, {b}");
Assert.NotVoid(a, b);
Assert.IsPrimitive(a, b);
Assert.IsInteger(a, b);
return new Value(a.Dynamic ^ b.Dynamic);
}
public static Value BitwiseLeft(Value a, Value b)
{
_logger.Method($"{a}, {b}");
Assert.NotVoid(a, b);
Assert.IsPrimitive(a, b);
Assert.IsInteger(a, b);
return new Value(a.Dynamic << (int)(b.Dynamic ?? 0));
}
public static Value BitwiseRight(Value a, Value b)
{
_logger.Method($"{a}, {b}");
Assert.NotVoid(a, b);
Assert.IsPrimitive(a, b);
Assert.IsInteger(a, b);
return new Value(a.Dynamic >> (int)(b.Dynamic ?? 0));
}
public static Value BitwiseInvert(Value a)
{
_logger.Method($"{a}");
Assert.NotVoid(a);
Assert.IsPrimitive(a);
Assert.IsInteger(a);
return new Value(~a.Dynamic);
}
}

View File

@ -1,5 +1,9 @@
namespace Qrakhen.Qamp.Core.Values;
/// <summary>
/// todo: make value type a byte instead to save some memory on values
/// edit: if that's even possible with c#'s memory padding
/// </summary>
[Flags]
public enum ValueType
{
@ -11,6 +15,7 @@ public enum ValueType
Decimal = 0x0008,
Bool = 0x0010,
Number = Unsigned | Signed | Decimal,
Integer = Unsigned | Signed | Char,
Primitive = Integer | Decimal | Bool,
@ -29,7 +34,7 @@ public enum ValueType
Instance = Object | 0x0020,
Class = Object | 0x0100,
Method = Class | Function,
PreValue = Object | 0x0200,
Outer = Object | 0x0200,
Module = 0xFFFF
}

View File

@ -0,0 +1,6 @@
namespace Qrakhen.Qamp.Digest;
public class Class1
{
}

View File

@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<BaseOutputPath>..\Build\</BaseOutputPath>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Qrakhen.Qamp.Core\Qrakhen.Qamp.Core.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,6 @@
namespace Qrakhen.Qamp.Runtime;
public class Class1
{
}

View File

@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<BaseOutputPath>..\Build\</BaseOutputPath>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Qrakhen.Qamp.Core\Qrakhen.Qamp.Core.csproj" />
</ItemGroup>
</Project>

View File

@ -1,12 +1,16 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 18
VisualStudioVersion = 18.0.11123.170 d18.0
VisualStudioVersion = 18.0.11123.170
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Qrakhen.Qamp.Core", "Qrakhen.Qamp.Core\Qrakhen.Qamp.Core.csproj", "{CA6F4D4E-AF35-4CA6-BC53-C83277EEE46C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Qrakhen.Qamp.CLI", "Qrakhen.Qamp.CLI\Qrakhen.Qamp.CLI.csproj", "{1D355F76-9D35-4CB2-BF8C-944B0842BC30}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Qrakhen.Qamp.Digest", "Qrakhen.Qamp.Digest\Qrakhen.Qamp.Digest.csproj", "{E836DAE4-9F53-4E3D-BEF7-ADA84BE356CE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Qrakhen.Qamp.Runtime", "Qrakhen.Qamp.Runtime\Qrakhen.Qamp.Runtime.csproj", "{4B0C0717-6529-4B70-A3EA-C3426F1B3FDC}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -21,6 +25,14 @@ Global
{1D355F76-9D35-4CB2-BF8C-944B0842BC30}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1D355F76-9D35-4CB2-BF8C-944B0842BC30}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1D355F76-9D35-4CB2-BF8C-944B0842BC30}.Release|Any CPU.Build.0 = Release|Any CPU
{E836DAE4-9F53-4E3D-BEF7-ADA84BE356CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E836DAE4-9F53-4E3D-BEF7-ADA84BE356CE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E836DAE4-9F53-4E3D-BEF7-ADA84BE356CE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E836DAE4-9F53-4E3D-BEF7-ADA84BE356CE}.Release|Any CPU.Build.0 = Release|Any CPU
{4B0C0717-6529-4B70-A3EA-C3426F1B3FDC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4B0C0717-6529-4B70-A3EA-C3426F1B3FDC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4B0C0717-6529-4B70-A3EA-C3426F1B3FDC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4B0C0717-6529-4B70-A3EA-C3426F1B3FDC}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

BIN
icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB