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> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<BaseOutputPath>..\Build\</BaseOutputPath>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <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; protected T[] Data;
public long Count { get; protected set; }= 0; public long Count { get; protected set; } = 0;
public long Capacity => Data.Length; public long Capacity => Data.Length;
protected Expander(int capacity = 0x10) protected Expander(int capacity = 0x10)

View File

@ -2,6 +2,10 @@
namespace Qrakhen.Qamp.Core.Collections; 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> public class FixedArray<T> : IEnumerable<T>, IGetSet<long, T>
{ {
protected T[] Data; 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; 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; private readonly T[] _data;

View File

@ -1,8 +1,15 @@
namespace Qrakhen.Qamp.Core.Collections; 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]; Data = new T[initialCapacity];
} }
@ -15,20 +22,22 @@ public class StackRegister<T> : Expander<T>, IToArray<T>
return default; return default;
} }
public void Add(T[] array) public void Push(T[] array)
{ {
foreach (T value in array) { foreach (T value in array) {
Add(value); Add(value);
} }
} }
public void Add(IEnumerable<T> array) public void Push(IEnumerable<T> array)
{ {
foreach (T value in array) { foreach (T value in array) {
Add(value); Add(value);
} }
} }
public long Push(T value) => Add(value);
public bool TryGet(long index, out T value) public bool TryGet(long index, out T value)
{ {
value = default; 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> : public class Register<TKey, TValue> :
IGetSet<TKey, TValue> IGetSet<TKey, TValue>,
IToArray<TValue>,
IEnumerable<KeyValuePair<TKey, TValue>>
where TKey : notnull where TKey : notnull
{ {
private readonly Dictionary<TKey, TValue> _data = new(); private readonly Dictionary<TKey, TValue> _data = new();
@ -19,7 +26,21 @@ public class Register<TKey, TValue> :
return key; return key;
} }
public bool TryGet(TKey key, out TValue? value) => _data.TryGetValue(key, out value);
public TValue Get(TKey key) => _data[key]; public TValue Get(TKey key) => _data[key];
public void Set(TKey key, TValue value) => _data[key] = value; public void Set(TKey key, TValue value) => _data[key] = value;
public bool Has(TKey key) => _data.ContainsKey(key); 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; 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>, public class StackLike<T> : Expander<T>,
IPush<long, T>, IPush<long, T>,
IPop<T>, IPop<T>,
ISeekable<long, T>, ISeekable<long, T>,
IPeekable<T> IPeekable<T>,
IToArray<T>
{ {
public long Position => Count; public long Position => Count;
@ -39,4 +44,14 @@ public class StackLike<T> : Expander<T>,
public bool CanPeek(int delta = 0) public bool CanPeek(int delta = 0)
=> Count + delta > -1 && Count + delta < Count; => 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 class Builder
{ {
public Builder? Outer; public Builder? Outer { get; init; }
public FunctionBuilder Function = new(); public FunctionBuilder Function = new();
public StackLike<Local> Locals = []; public StackLike<Local> Locals = [];
public StackLike<Outer> Outers = []; public StackLike<Outer> Outers = [];
public int ScopeDepth = 0; 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; 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 PushStack<Instruction> Instructions = new();
public readonly StackRegister<Value> Constants = new(); public readonly PushStack<Value> Constants = new();
public Segment Build() public Segment Build()
{ {

View File

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

View File

@ -52,8 +52,8 @@ public static class ExpressionParser
static void Call(Digester digester, bool canAssign) static void Call(Digester digester, bool canAssign)
{ {
byte count = digester.ArgumentList(); byte count = digester.Arguments();
digester.Emit(OpCode.Call, count); digester.Emit(OpCode.Invoke, count);
} }
static void Dot(Digester digester, bool canAssign) static void Dot(Digester digester, bool canAssign)
@ -62,14 +62,14 @@ public static class ExpressionParser
long name = digester.IdentifierConstant(digester.Previous.Value); long name = digester.IdentifierConstant(digester.Previous.Value);
if (canAssign && digester.Match(TokenType.Equal)) { if (canAssign && digester.Match(TokenType.Equal)) {
digester.Expression(); digester.ParseExpression();
digester.EmitDynamic(OpCode.SetProperty, name); digester.EmitDynamic(OpCode.SetMember, name);
} else if (digester.Match(TokenType.GroupOpen)) { } else if (digester.Match(TokenType.GroupOpen)) {
Byte argCount = 0; //argumentList(); byte argCount = digester.Arguments();
digester.Emit(OpCode.Invoke, name.GetBytes()); digester.EmitDynamic(OpCode.InvokeMember, name);
digester.EmitDynamic(argCount); digester.Emit(argCount);
} else { } 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) static void Group(Digester digester, bool canAssign)
{ {
digester.Expression(); digester.ParseExpression();
digester.Consume(TokenType.GroupClose, "Expected ')' after expression."); digester.Consume(TokenType.GroupClose, "Expected ')' after expression.");
} }
@ -100,7 +100,7 @@ public static class ExpressionParser
{ {
int length = 0; int length = 0;
while (!digester.Check(TokenType.ArrayClose)) { while (!digester.Check(TokenType.ArrayClose)) {
digester.Expression(); digester.ParseExpression();
length++; length++;
if (digester.Check(TokenType.Comma)) if (digester.Check(TokenType.Comma))
digester.Next(); digester.Next();
@ -113,19 +113,19 @@ public static class ExpressionParser
static void ArrayAdd(Digester digester, bool canAssign) static void ArrayAdd(Digester digester, bool canAssign)
{ {
digester.Expression(); digester.ParseExpression();
digester.Emit(OpCode.ArrayAdd); digester.Emit(OpCode.ArrayAdd);
} }
static void Index(Digester digester, bool canAssign) static void Index(Digester digester, bool canAssign)
{ {
digester.Expression(); digester.ParseExpression();
digester.Consume(TokenType.ArrayClose, "expected ] after array accessor"); digester.Consume(TokenType.ArrayClose, "expected ] after array accessor");
if (canAssign && digester.Match(TokenType.ArrayAdd)) { if (canAssign && digester.Match(TokenType.ArrayAdd)) {
digester.Expression(); digester.ParseExpression();
digester.Emit(OpCode.ArrayAdd); digester.Emit(OpCode.ArrayAdd);
} else if (canAssign && digester.Match(TokenType.Equal)) { } else if (canAssign && digester.Match(TokenType.Equal)) {
digester.Expression(); digester.ParseExpression();
digester.Emit(OpCode.ArraySet); digester.Emit(OpCode.ArraySet);
} else { } else {
digester.Emit(OpCode.ArrayGet); digester.Emit(OpCode.ArrayGet);
@ -183,7 +183,7 @@ public static class ExpressionParser
} }
if (canAssign && digester.Match(TokenType.Equal)) { if (canAssign && digester.Match(TokenType.Equal)) {
digester.Expression(); digester.ParseExpression();
digester.Emit(Set); digester.Emit(Set);
digester.EmitDynamic(variable); digester.EmitDynamic(variable);
} else { } else {
@ -211,9 +211,9 @@ public static class ExpressionParser
Variable(digester, "this", false); Variable(digester, "this", false);
if (digester.Match(TokenType.GroupOpen)) if (digester.Match(TokenType.GroupOpen))
{ {
byte arguments = digester.ArgumentList(); byte arguments = digester.Arguments();
Variable(digester, "base", false); Variable(digester, "base", false);
digester.Emit(OpCode.BaseCall); digester.Emit(OpCode.InvokeBase);
digester.Emit(arguments); digester.Emit(arguments);
} else { } else {
Variable(digester, "base", false); Variable(digester, "base", false);
@ -294,7 +294,7 @@ public static class ExpressionParser
_rules[TokenType.Export] = new Rule(null, null, Weight.None); _rules[TokenType.Export] = new Rule(null, null, Weight.None);
_rules[TokenType.Import] = 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.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.This] = new Rule(This, null, Weight.None);
_rules[TokenType.True] = new Rule(Literal, null, Weight.None); _rules[TokenType.True] = new Rule(Literal, null, Weight.None);
_rules[TokenType.Var] = new Rule(null, 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.Tokenization;
using Qrakhen.Qamp.Core.Values; using Qrakhen.Qamp.Core.Values;
using V = Qrakhen.Qamp.Core.Values.ValueType;
namespace Qrakhen.Qamp.Core; namespace Qrakhen.Qamp.Core;
public class QampException(string message, TokenPosition position = default) public class QampException(string message, object? context = null)
: Exception(message) : Exception(message)
{ {
public readonly TokenPosition Position = position; public readonly object? Context = context;
} }
public class ReaderException(string message, IReader<Token> reader) 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 readonly Token Token = token;
} }
public class ExecutionException(string message, TokenPosition position = default) public class RuntimeException(string message, object? context = null)
: QampException(message, position); : QampException(message, context);
public class DivisionByZeroException(string? message, TokenPosition position = default) public class DivisionByZeroException(string? message, object? context = null)
: ExecutionException(message ?? "Can not divide by zero", position); : RuntimeException(message ?? "Can not divide by zero", context);
public class ConversionException(string message, Value value, TokenPosition position = default) public class ConversionException(string message, Value value, object? context = null)
: ExecutionException(message, position) : RuntimeException(message, context)
{ {
public readonly Value Value = value; 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 public enum OpCode
{ {
None = 0, F_Mask = 0xF0,
Constant,
Null, None = 0x00,
True, Constant = 0x01,
False, Null = 0x02,
Pop, Pop = 0x03,
Export, Cast = 0x04,
Import,
GetLocal, False = 0x0e,
SetLocal, True = 0x0f,
GetGlobal,
DefineGlobal, Val = 0x10,
SetGlobal, Ref = 0x11,
GetOuter,
SetOuter, SetGlobal = 0x20,
GetProperty, GetGlobal = 0x21,
SetProperty, GetLocal = 0x22,
Property, SetLocal = 0x23,
Base, GetOuter = 0x24,
Equal, SetOuter = 0x25,
Greater, GetMember = 0x26,
Less, SetMember = 0x27,
Add,
Subtract, DefineGlobal = 0x30,
Multiply, CloseOuter = 0x31,
Divide,
Modulo, AssignValue = 0x40, // unsure
BitwiseAnd, AssignReference = 0x41, // unsure
BitwiseOr,
BitwiseXor, F_Operation = 0x60,
BitwiseNot, Add = 0x60,
BitwiseLeft, Subtract = 0x61,
BitwiseRight, Multiply = 0x62,
Increment, Divide = 0x63,
Decrement, Modulo = 0x64,
Not, BitwiseAnd = 0x65,
Negate, BitwiseOr = 0x66,
Ref, BitwiseXor = 0x67,
Array, BitwiseLeft = 0x68,
ArrayGet, BitwiseRight = 0x69,
ArraySet, F_Unary = 0x0a,
ArrayAdd, Not = 0x6a,
ArrayRemove, Negate = 0x6b,
Print, BitwiseNot = 0x6c,
PrintExpr,
Typeof, F_Compare = 0x50,
Jump, Equal = 0x50,
JumpIfFalse, Greater = 0x51,
Loop, Less = 0x52,
Call,
Invoke, Increment = 0x70,
BaseCall, Decrement = 0x71,
Closure,
CloseUpvalue, Array = 0xc0,
Return, ArrayGet = 0xc1,
Class, ArraySet = 0xc2,
Inherit, ArrayAdd = 0xc3,
Method 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.Collections;
using Qrakhen.Qamp.Core.Compilation;
using Qrakhen.Qamp.Core.Logging; using Qrakhen.Qamp.Core.Logging;
using Qrakhen.Qamp.Core.Tokenization; using Qrakhen.Qamp.Core.Tokenization;
using Qrakhen.Qamp.Core.Values; using Qrakhen.Qamp.Core.Values;
using Qrakhen.Qamp.Core.Values.Objects; using Qrakhen.Qamp.Core.Values.Objects;
using System.Xml.Linq;
using String = Qrakhen.Qamp.Core.Values.Objects.String; using String = Qrakhen.Qamp.Core.Values.Objects.String;
namespace Qrakhen.Qamp.Core.Execution; namespace Qrakhen.Qamp.Core.Execution;
using Op = OpCode; using Op = OpCode;
using T = Values.ValueType;
/*
* 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;
}
}
public class Call public class Call
{ {
public Closure Closure; public Context Context;
public InstructionPtr Instruction; public InstructionPtr Instruction;
public Pointer<Value> StackPtr; public Pointer<Value> StackPtr;
public Call(Closure closure, StackLike<Value> stack, long stackOffset) public Call(Context context, StackLike<Value> stack, long stackOffset)
{ {
Closure = closure; Context = context;
Instruction = new InstructionPtr(Closure.Function.Segment); Instruction = new InstructionPtr(Context.Function.Segment);
StackPtr = new Pointer<Value>(stack, stackOffset); StackPtr = new Pointer<Value>(stack, stackOffset);
} }
} }
public unsafe class OuterWrapper
{
public Value* Target;
public Value Stored;
}
public class Runner : IDisposable public class Runner : IDisposable
{ {
private readonly ILogger _logger = LoggerService.Get<Runner>(); private readonly ILogger _logger = LoggerService.Get<Runner>();
public readonly Options Options; 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... ? // todo: maybe have a pointer everywhere to stacks or arrays and use those rather than built-in pointers or cursors... ?
public StackLike<Value> Stack; private StackLike<Value> Stack;
public long Cursor; private long Cursor => Stack.Position;
public Register<string, Value> Globals = new Register<string, Value>(); private List<Outer> Outers = [];
public Register<string, Value> Imports = new Register<string, Value>();
public Register<string, Value> Exports = new Register<string, Value>(); private Register<string, Value> Globals = new();
private Register<string, Value> Imports = new();
private Register<string, Value> Exports = new();
public Runner(Options options) public Runner(Options options)
{ {
@ -172,39 +54,74 @@ public class Runner : IDisposable
{ {
_logger.Method(); _logger.Method();
using Reader reader = new Reader(stream); using Reader reader = new Reader(stream);
Digester digester = new Digester(reader); Compilation.Digester digester = new(reader);
Function function = digester.Digest(); Function function = digester.Digest();
File.WriteAllBytes("func.sqi", function.Segment.Serialize()); File.WriteAllBytes("func.sqi", function.Segment.Serialize());
Closure closure = new Closure(function); Context closure = new Context(function);
Push(Obj.Create(closure)); Push(Obj.Create(closure));
Call(closure, 0); Invoke(closure, 0);
return Interpret(); return Execute();
} }
private ExecutionResult Interpret() private bool TestFlag(Op code, Op flag) => (code & Op.F_Mask) == flag;
private ExecutionResult Execute()
{ {
_logger.Method(); _logger.Method();
Call call = Calls.Peek(-1); Call call = Calls.Peek(-1);
do { do {
Op opCode = call.Instruction.Next(); Op opCode = call.Instruction.Next();
_logger.Verbose($"OpCode: {opCode}"); _logger.Verbose($"OpCode: {opCode}");
switch (opCode) {
case Op.Constant: Push(call.Instruction.NextConstant()); break;
case Op.Pop: Pop(); break;
case Op.Not: OpNot(); break; if (TestFlag(opCode, Op.F_Operation)) {
case Op.Add: OpAdd(); break; Value result = ValueOperation.Resolve(PopOperation(opCode));
case Op.Subtract: OpSubtract(); break; Push(result);
case Op.Divide: OpDivide(); break; continue;
case Op.Modulo: OpModulo(); break; }
case Op.Multiply: OpMultiply(); break;
case Op.Negate: OpNegate(); break; if (TestFlag(opCode, Op.F_Compare)) {
case Op.BitwiseOr: OpBitwiseOr(); break; Value result = ValueOperation.Resolve(PopOperation(opCode));
case Op.BitwiseAnd: OpBitwiseAnd(); break; Push(result);
case Op.BitwiseXor: OpBitwiseXor(); break; continue;
case Op.BitwiseNot: OpBitwiseNot(); break; }
case Op.BitwiseLeft: OpBitwiseLeft(); break;
case Op.BitwiseRight: OpBitwiseRight(); break; 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: { case Op.GetLocal: {
long slot = call.Instruction.NextDynamic(); long slot = call.Instruction.NextDynamic();
@ -220,38 +137,158 @@ public class Runner : IDisposable
break; break;
} }
case Op.DefineGlobal: { case Op.GetOuter: {
string? name = call.Instruction.GetStringConstant(call.Instruction.NextDynamic())?.Value; long slot = call.Instruction.NextDynamic();
if (string.IsNullOrEmpty(name)) Push(call.Context.Outers[slot].Value);
throw new ExecutionException($"tried to define global variable with empty name");
Globals[name] = Pop();
_logger.Verbose($"defined global {name} as {Globals[name]}");
break; break;
} }
case Op.SetGlobal: { case Op.SetOuter: {
string? name = call.Instruction.GetStringConstant(call.Instruction.NextDynamic())?.Value; long slot = call.Instruction.NextDynamic();
if (string.IsNullOrEmpty(name)) call.Context.Outers[slot].Value = Peek();
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]}");
break; break;
} }
case Op.GetGlobal: { case Op.GetMember: {
string? name = call.Instruction.GetStringConstant(call.Instruction.NextDynamic())?.Value; Value value = Peek();
if (string.IsNullOrEmpty(name)) if (!value.Is(T.Instance))
throw new ExecutionException($"tried to set global variable with empty name"); return Error("Only instances have members.");
if (!Globals.Has(name)) Instance instance = value.Ptr.As<Instance>()!;
Push(Value.Void); string name = call.Instruction.NextString().Value!;
else if (instance.Values.TryGet(name, out Value member)) {
Push(Globals[name]); Pop(); // remove instance from stack
_logger.Verbose($"got global {name} ({Peek()})"); Push(member);
} else if (!BindMethod(instance.Class, name)) {
return Error($"Could not bind member {name} to instance {instance} of class {instance.Class.Name}!");
}
break; 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: { case Op.Typeof: {
Value value = Pop(); Value value = Pop();
@ -265,17 +302,22 @@ public class Runner : IDisposable
break; break;
} }
case Op.Return: // todo: not done case Op.PrintStack: {
Value result = Pop(); for (int i = 0; i < Cursor; i++) {
Calls.Pop(); Console.WriteLine($"{Stack[i].ToString(true)}");
if (Calls.Count == 0) {
Pop();
return ExecutionResult.OK;
} }
Cursor = call.StackPtr.Ptr; // well find a better way...
Push(result);
call = Calls.Peek();
break; 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); } while (call.Instruction.Segment.Instructions.Length > call.Instruction.Ptr);
@ -283,7 +325,34 @@ public class Runner : IDisposable
return ExecutionResult.OK; 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(); _logger.Method();
if (argumentCount != closure.Function.ArgumentCount) if (argumentCount != closure.Function.ArgumentCount)
@ -297,6 +366,90 @@ public class Runner : IDisposable
return true; 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) private Value Peek(int delta = -1)
{ {
_logger.Method(); _logger.Method();
@ -315,92 +468,23 @@ public class Runner : IDisposable
Stack.Push(value); Stack.Push(value);
} }
private void OpNot() private Operation PopOperation(Op code)
{
Value value = Pop();
Push(ValueOperation.Not(value));
}
private void OpAdd()
{ {
_logger.Method(code);
if ((code & Op.F_Unary) == Op.F_Unary)
return new Operation(code, Pop(), default);
Value right = Pop(); Value right = Pop();
Value left = 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(); _logger.Method(message);
Value left = Pop(); _logger.Error(message, context);
Push(ValueOperation.Subtract(left, right)); if (@throw)
} throw new QampException(message, context);
return ExecutionResult.Execution;
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));
} }
public void Dispose() public void Dispose()
@ -409,6 +493,11 @@ public class Runner : IDisposable
} }
} }
public class GarbageCollector
{
}
public enum ExecutionResult public enum ExecutionResult
{ {
OK = 0x0000, OK = 0x0000,

View File

@ -29,18 +29,27 @@ public class Segment(
} }
/// <summary> /// <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> /// </summary>
/// <param name="offset"></param> /// <remarks>
/// <returns></returns> /// All of this is done to compress compiled instruction size.
public long ReadDynamic(long offset, out byte length) /// 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 value = Read(offset);
byte[] bytes = new byte[8]; read = 1;
for (int i = 0; i < length; i++) { if (value < 0x80) // 0x80 flag for length, less than 0x80 means direct read
bytes[i] = Instructions[offset + 1 + i]; return value;
} int length = value ^ 0x80;
return bytes.ToInt64(); 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)); 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> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<BaseOutputPath>..\Build\</BaseOutputPath>
</PropertyGroup> </PropertyGroup>
</Project> </Project>

View File

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

View File

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

View File

@ -87,10 +87,13 @@ public enum TokenType
Ref, Ref,
Function, Function,
Class, Class,
Super, Base,
TypeOf,
Print, Print,
PrintStack,
PrintGlobals,
PrintExpr,
TypeOf,
Import, Import,
Export 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.Collections;
using Qrakhen.Qamp.Core.Execution;
namespace Qrakhen.Qamp.Core.Values.Objects; 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 Function Function = function;
public StackRegister<OuterWrapper> PreValues = new(); public PushStack<Outer> Outers = new();
public override string ToString() => "{}"; 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 OuterCount = outerCount;
public readonly int ArgumentCount = argumentCount; 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 Qrakhen.Qamp.Core.Values.Objects;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Qrakhen.Qamp.Core.Collections; using Qrakhen.Qamp.Core.Collections;
using System.Diagnostics.CodeAnalysis;
namespace Qrakhen.Qamp.Core.Values; namespace Qrakhen.Qamp.Core.Values;
[StructLayout(LayoutKind.Sequential, Size = sizeof(ulong))] [StructLayout(LayoutKind.Sequential, Size = sizeof(ulong))]
public readonly struct Ptr 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; [Serialized] public readonly Address Address;
@ -31,5 +32,13 @@ public readonly struct Ptr
Address = _register.Add(obj); 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}"; public override string ToString() => $"0x{Value}";
} }

View File

@ -15,6 +15,8 @@ public interface IValue
public readonly struct Value : IValue, IDebug<string> public readonly struct Value : IValue, IDebug<string>
{ {
public static readonly Value Void = new Value(); 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 ulong Unsigned;
[FieldOffset(0x00)] [Serialized] public readonly long Signed; [FieldOffset(0x00)] [Serialized] public readonly long Signed;
@ -28,9 +30,7 @@ public readonly struct Value : IValue, IDebug<string>
[FieldOffset(0x08)] [Serialized] public readonly T Type; [FieldOffset(0x08)] [Serialized] public readonly T Type;
[FieldOffset(0x0c)] [Serialized] public readonly uint Meta; // like lengths of arrays or lists [FieldOffset(0x0c)] [Serialized] public readonly uint Meta; // like lengths of arrays or lists
public T ValueType => Type;
private Value(T type) => Type = type; private Value(T type) => Type = type;
public Value() : this(T.Void) { } public Value() : this(T.Void) { }
@ -44,7 +44,20 @@ public readonly struct Value : IValue, IDebug<string>
public Value(Ref @ref) : this(T.Reference) => Ref = @ref; public Value(Ref @ref) : this(T.Reference) => Ref = @ref;
public dynamic? Dynamic => AsDynamic(); 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) public bool Is(T type, bool exact = true)
{ {
return exact ? (Type & type) == type : (Type & type) > 0; 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) 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 Ptr.Value;
return Type switch { return Type switch {
@ -88,13 +104,18 @@ public readonly struct Value : IValue, IDebug<string>
public override int GetHashCode() => Unsigned.GetHashCode(); 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(); string type = Type.ToString();
if (Type.HasFlag(T.Pointer)) if (Type.HasFlag(T.Pointer))
type = Ptr.Value?.Type.ToString() ?? "NullPtr"; type = Ptr.Value?.Type.ToString() ?? "NullPtr";
return $"[{type}, {AsDynamic(false) ?? "null"}]"; return $"<{type}, {AsDynamic(false) ?? "null"}>";
} }
public string Debug(DebugLevel level = DebugLevel.None) 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; 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] [Flags]
public enum ValueType public enum ValueType
{ {
@ -11,6 +15,7 @@ public enum ValueType
Decimal = 0x0008, Decimal = 0x0008,
Bool = 0x0010, Bool = 0x0010,
Number = Unsigned | Signed | Decimal,
Integer = Unsigned | Signed | Char, Integer = Unsigned | Signed | Char,
Primitive = Integer | Decimal | Bool, Primitive = Integer | Decimal | Bool,
@ -29,7 +34,7 @@ public enum ValueType
Instance = Object | 0x0020, Instance = Object | 0x0020,
Class = Object | 0x0100, Class = Object | 0x0100,
Method = Class | Function, Method = Class | Function,
PreValue = Object | 0x0200, Outer = Object | 0x0200,
Module = 0xFFFF 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 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 18 # Visual Studio Version 18
VisualStudioVersion = 18.0.11123.170 d18.0 VisualStudioVersion = 18.0.11123.170
MinimumVisualStudioVersion = 10.0.40219.1 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}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Qrakhen.Qamp.Core", "Qrakhen.Qamp.Core\Qrakhen.Qamp.Core.csproj", "{CA6F4D4E-AF35-4CA6-BC53-C83277EEE46C}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Qrakhen.Qamp.CLI", "Qrakhen.Qamp.CLI\Qrakhen.Qamp.CLI.csproj", "{1D355F76-9D35-4CB2-BF8C-944B0842BC30}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Qrakhen.Qamp.CLI", "Qrakhen.Qamp.CLI\Qrakhen.Qamp.CLI.csproj", "{1D355F76-9D35-4CB2-BF8C-944B0842BC30}"
EndProject 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 Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU 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}.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.ActiveCfg = Release|Any CPU
{1D355F76-9D35-4CB2-BF8C-944B0842BC30}.Release|Any CPU.Build.0 = 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 EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE

BIN
icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB