diff --git a/Qrakhen.Qamp.CLI/Qrakhen.Qamp.CLI.csproj b/Qrakhen.Qamp.CLI/Qrakhen.Qamp.CLI.csproj index 48b6eb8..941e7a1 100644 --- a/Qrakhen.Qamp.CLI/Qrakhen.Qamp.CLI.csproj +++ b/Qrakhen.Qamp.CLI/Qrakhen.Qamp.CLI.csproj @@ -2,9 +2,10 @@ Exe - net8.0 + net10.0 enable enable + ..\Build\ diff --git a/Qrakhen.Qamp.CLI/TODO.md b/Qrakhen.Qamp.CLI/TODO.md new file mode 100644 index 0000000..e2e1941 --- /dev/null +++ b/Qrakhen.Qamp.CLI/TODO.md @@ -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 diff --git a/Qrakhen.Qamp.Core/Collections/Expander.cs b/Qrakhen.Qamp.Core/Collections/Expander.cs index db0029d..95a8eac 100644 --- a/Qrakhen.Qamp.Core/Collections/Expander.cs +++ b/Qrakhen.Qamp.Core/Collections/Expander.cs @@ -17,7 +17,7 @@ public abstract class Expander : { 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) diff --git a/Qrakhen.Qamp.Core/Collections/FixedArray.cs b/Qrakhen.Qamp.Core/Collections/FixedArray.cs index 501cb31..95802a8 100644 --- a/Qrakhen.Qamp.Core/Collections/FixedArray.cs +++ b/Qrakhen.Qamp.Core/Collections/FixedArray.cs @@ -2,6 +2,10 @@ namespace Qrakhen.Qamp.Core.Collections; +/// +/// Basically just a normal array, but implements to be used with . +/// +/// public class FixedArray : IEnumerable, IGetSet { protected T[] Data; diff --git a/Qrakhen.Qamp.Core/Collections/Pointer.cs b/Qrakhen.Qamp.Core/Collections/Pointer.cs new file mode 100644 index 0000000..1229797 --- /dev/null +++ b/Qrakhen.Qamp.Core/Collections/Pointer.cs @@ -0,0 +1,46 @@ +namespace Qrakhen.Qamp.Core.Collections; + +/// +/// Dynamic Pointer able to point to any object implementing the interface. +/// +public class Pointer +{ + protected IGetSet Target; + public long Ptr; + + public Pointer(IGetSet target, long pointer = 0) + { + Target = target; + Ptr = pointer; + } + + public Pointer Branch(long delta = 0) + { + return new Pointer(Target, Ptr + delta); + } + + /// + /// Returns the next item from and increases the pointer by 1. + /// + public T Next() => Target.Get(Ptr++); + + /// + /// Sets the value of at . + /// + public void Set(long position, T value) => Target.Set(position, value); + + /// + /// Sets the value of at the current location. + /// + public void Set(T value) => Set(Ptr, value); + + /// + /// Gets the value of at . + /// + public T Get(long position) => Target.Get(position); + + /// + /// Gets the value of at the current location. + /// + public T Get() => Get(Ptr); +} diff --git a/Qrakhen.Qamp.Core/Collections/PopStack.cs b/Qrakhen.Qamp.Core/Collections/PopStack.cs index fc22283..e1bc3a6 100644 --- a/Qrakhen.Qamp.Core/Collections/PopStack.cs +++ b/Qrakhen.Qamp.Core/Collections/PopStack.cs @@ -2,7 +2,13 @@ namespace Qrakhen.Qamp.Core.Collections; -public class PopStack : IEnumerable, IPop, IPeekable +/// +/// Pop-Only stack with a fixed array that is gradually reduced. +/// +public class PopStack : + IEnumerable, + IPop, + IPeekable { private readonly T[] _data; diff --git a/Qrakhen.Qamp.Core/Collections/StackRegister.cs b/Qrakhen.Qamp.Core/Collections/PushStack.cs similarity index 69% rename from Qrakhen.Qamp.Core/Collections/StackRegister.cs rename to Qrakhen.Qamp.Core/Collections/PushStack.cs index 427bced..bcb2a35 100644 --- a/Qrakhen.Qamp.Core/Collections/StackRegister.cs +++ b/Qrakhen.Qamp.Core/Collections/PushStack.cs @@ -1,8 +1,15 @@ namespace Qrakhen.Qamp.Core.Collections; -public class StackRegister : Expander, IToArray +/// +/// Push-only Stack that is based on . +/// +/// +public class PushStack : + Expander, + IPush, + IToArray { - public StackRegister(int initialCapacity = 0x10) + public PushStack(int initialCapacity = 0x10) { Data = new T[initialCapacity]; } @@ -15,20 +22,22 @@ public class StackRegister : Expander, IToArray return default; } - public void Add(T[] array) + public void Push(T[] array) { foreach (T value in array) { Add(value); } } - public void Add(IEnumerable array) + public void Push(IEnumerable 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; diff --git a/Qrakhen.Qamp.Core/Collections/Register.cs b/Qrakhen.Qamp.Core/Collections/Register.cs index d070eaa..c2f16bb 100644 --- a/Qrakhen.Qamp.Core/Collections/Register.cs +++ b/Qrakhen.Qamp.Core/Collections/Register.cs @@ -1,7 +1,14 @@ -namespace Qrakhen.Qamp.Core.Collections; +using System.Collections; +namespace Qrakhen.Qamp.Core.Collections; + +/// +/// Dictionary wrapper implementing . +/// public class Register : - IGetSet + IGetSet, + IToArray, + IEnumerable> where TKey : notnull { private readonly Dictionary _data = new(); @@ -19,7 +26,21 @@ public class Register : 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> GetEnumerator() + { + foreach (var item in _data) + yield return item; + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } } diff --git a/Qrakhen.Qamp.Core/Collections/StackLike.cs b/Qrakhen.Qamp.Core/Collections/StackLike.cs index 986afe9..5a0c682 100644 --- a/Qrakhen.Qamp.Core/Collections/StackLike.cs +++ b/Qrakhen.Qamp.Core/Collections/StackLike.cs @@ -1,10 +1,15 @@ namespace Qrakhen.Qamp.Core.Collections; +/// +/// Stack-like implementation with a few added features like seeking and setting values. +/// +/// public class StackLike : Expander, IPush, IPop, ISeekable, - IPeekable + IPeekable, + IToArray { public long Position => Count; @@ -39,4 +44,14 @@ public class StackLike : Expander, public bool CanPeek(int delta = 0) => Count + delta > -1 && Count + delta < Count; + + /// + /// Decimates this stack down to the given position, overwriting everything beyond that when pushing in the future. + /// + public void Decimate(long position) + { + Count = position; + } + + public T[] ToArray() => Data.ToArray(); } diff --git a/Qrakhen.Qamp.Core/Compilation/Builders/Builder.cs b/Qrakhen.Qamp.Core/Compilation/Builders/Builder.cs index 8c73496..e242c9c 100644 --- a/Qrakhen.Qamp.Core/Compilation/Builders/Builder.cs +++ b/Qrakhen.Qamp.Core/Compilation/Builders/Builder.cs @@ -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 Locals = []; public StackLike Outers = []; public int ScopeDepth = 0; + + public Builder(Builder? outer = null) + { + Outer = outer; + } } \ No newline at end of file diff --git a/Qrakhen.Qamp.Core/Compilation/Builders/IBuilder.cs b/Qrakhen.Qamp.Core/Compilation/Builders/IBuilder.cs new file mode 100644 index 0000000..f0233d6 --- /dev/null +++ b/Qrakhen.Qamp.Core/Compilation/Builders/IBuilder.cs @@ -0,0 +1,6 @@ +namespace Qrakhen.Qamp.Core.Compilation.Builders; + +public interface IBuilder +{ + public TOut Build(); +} \ No newline at end of file diff --git a/Qrakhen.Qamp.Core/Compilation/Builders/SegmentBuilder.cs b/Qrakhen.Qamp.Core/Compilation/Builders/SegmentBuilder.cs index d2c71b3..a2da2aa 100644 --- a/Qrakhen.Qamp.Core/Compilation/Builders/SegmentBuilder.cs +++ b/Qrakhen.Qamp.Core/Compilation/Builders/SegmentBuilder.cs @@ -4,10 +4,10 @@ using Qrakhen.Qamp.Core.Values; namespace Qrakhen.Qamp.Core.Compilation.Builders; -public readonly struct SegmentBuilder() : IDebug +public class SegmentBuilder() : IBuilder, IDebug { - public readonly StackRegister Instructions = new(); - public readonly StackRegister Constants = new(); + public readonly PushStack Instructions = new(); + public readonly PushStack Constants = new(); public Segment Build() { diff --git a/Qrakhen.Qamp.Core/Compilation/Digester.cs b/Qrakhen.Qamp.Core/Compilation/Digester.cs index 2ec5c98..0440924 100644 --- a/Qrakhen.Qamp.Core/Compilation/Digester.cs +++ b/Qrakhen.Qamp.Core/Compilation/Digester.cs @@ -37,6 +37,11 @@ public class DigesterProvider public object ExpressionDigester; // etc. } +public abstract class xDigester +{ + +} + public class Digester : ISteppable { private readonly CompilerState _compiler; @@ -105,7 +110,7 @@ public class Digester : ISteppable { Emit(Op.Return); Function function = Builder.Function.Build(); - Builder = Builder.Outer!; + Builder = Builder.Outer; return function; } @@ -114,8 +119,8 @@ public class Digester : ISteppable _logger.Method(); if (Match(T.Class)) DeclareClass(); else if (Match(T.Function)) DeclareFunction(); - else if (Match(T.Var)) DeclareVariable(); - else Statement(); + else if (Match(T.Var)) DeclareVariable(); + else Statement(); } internal void Statement() @@ -129,15 +134,19 @@ public class Digester : ISteppable 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 // 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 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 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 _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 { _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 _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 } 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 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 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,10 +434,10 @@ public class Digester : ISteppable 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 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 } } - 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 /// 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); } diff --git a/Qrakhen.Qamp.Core/Compilation/ExpressionParser.cs b/Qrakhen.Qamp.Core/Compilation/ExpressionParser.cs index cbadfa9..a0d58d5 100644 --- a/Qrakhen.Qamp.Core/Compilation/ExpressionParser.cs +++ b/Qrakhen.Qamp.Core/Compilation/ExpressionParser.cs @@ -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); diff --git a/Qrakhen.Qamp.Core/Exceptions.cs b/Qrakhen.Qamp.Core/Exceptions.cs index 57bf192..bd27ed8 100644 --- a/Qrakhen.Qamp.Core/Exceptions.cs +++ b/Qrakhen.Qamp.Core/Exceptions.cs @@ -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 reader) @@ -23,14 +21,14 @@ public class TokenException(string message, IReader 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; } \ No newline at end of file diff --git a/Qrakhen.Qamp.Core/Execution/InstructionPtr.cs b/Qrakhen.Qamp.Core/Execution/InstructionPtr.cs new file mode 100644 index 0000000..968ad81 --- /dev/null +++ b/Qrakhen.Qamp.Core/Execution/InstructionPtr.cs @@ -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 +{ + 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()!; + 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; + } +} diff --git a/Qrakhen.Qamp.Core/Execution/OpCode.cs b/Qrakhen.Qamp.Core/Execution/OpCode.cs index 73fad7f..f503063 100644 --- a/Qrakhen.Qamp.Core/Execution/OpCode.cs +++ b/Qrakhen.Qamp.Core/Execution/OpCode.cs @@ -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 } \ No newline at end of file diff --git a/Qrakhen.Qamp.Core/Execution/Runner.cs b/Qrakhen.Qamp.Core/Execution/Runner.cs index 08d9aae..d99931d 100644 --- a/Qrakhen.Qamp.Core/Execution/Runner.cs +++ b/Qrakhen.Qamp.Core/Execution/Runner.cs @@ -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 - */ - -/// -/// Dynamic Pointer able to point to any object implementing the interface. -/// -public class Pointer -{ - protected IGetSet Target; - public long Ptr; - - public Pointer(IGetSet target, long pointer = 0) - { - Target = target; - Ptr = pointer; - } - - /// - /// Returns the next item from and increases the pointer by 1. - /// - public T Next() => Target.Get(Ptr++); - - /// - /// Sets the value of at . - /// - public void Set(long position, T value) => Target.Set(position, value); - - /// - /// Sets the value of at the current location. - /// - public void Set(T value) => Set(Ptr, value); - - /// - /// Gets the value of at . - /// - public T Get(long position) => Target.Get(position); - - /// - /// Gets the value of at the current location. - /// - public T Get() => Get(Ptr); -} - -// this is all a bit cheesy imho -public class InstructionPtr : Pointer -{ - 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 StackPtr; - public Call(Closure closure, StackLike stack, long stackOffset) + public Call(Context context, StackLike stack, long stackOffset) { - Closure = closure; - Instruction = new InstructionPtr(Closure.Function.Segment); + Context = context; + Instruction = new InstructionPtr(Context.Function.Segment); StackPtr = new Pointer(stack, stackOffset); } } -public unsafe class OuterWrapper -{ - public Value* Target; - public Value Stored; -} - public class Runner : IDisposable { private readonly ILogger _logger = LoggerService.Get(); public readonly Options Options; - public StackLike Calls; + private StackLike Calls; // todo: maybe have a pointer everywhere to stacks or arrays and use those rather than built-in pointers or cursors... ? - public StackLike Stack; - public long Cursor; + private StackLike Stack; + private long Cursor => Stack.Position; - public Register Globals = new Register(); - public Register Imports = new Register(); - public Register Exports = new Register(); + private List Outers = []; + + private Register Globals = new(); + private Register Imports = new(); + private Register 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()!; + 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()!; + 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()!; + 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()!; + 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()!; + 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(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 inheritingClass = Peek().Ptr.As()!; + 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 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 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()!; + 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()!; + 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()!; + 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()!; + Stack.Set(Cursor - argumentCount - 1, Instantiate(@class)); + if (@class.Members.TryGet(@class.Name, out Value ctor)) { + Context? context = ctor.Ptr.As()!; + 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()!, 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.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, diff --git a/Qrakhen.Qamp.Core/Execution/Segment.cs b/Qrakhen.Qamp.Core/Execution/Segment.cs index 91e9775..bedfdf7 100644 --- a/Qrakhen.Qamp.Core/Execution/Segment.cs +++ b/Qrakhen.Qamp.Core/Execution/Segment.cs @@ -29,18 +29,27 @@ public class Segment( } /// - /// 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).
+ /// 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). ///
- /// - /// - public long ReadDynamic(long offset, out byte length) + /// + /// 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. + /// + 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)); diff --git a/Qrakhen.Qamp.Core/Execution/ValueOperation.cs b/Qrakhen.Qamp.Core/Execution/ValueOperation.cs new file mode 100644 index 0000000..ab819da --- /dev/null +++ b/Qrakhen.Qamp.Core/Execution/ValueOperation.cs @@ -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 _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(); + _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); + } +} \ No newline at end of file diff --git a/Qrakhen.Qamp.Core/Injector.cs b/Qrakhen.Qamp.Core/Injector.cs new file mode 100644 index 0000000..7599da4 --- /dev/null +++ b/Qrakhen.Qamp.Core/Injector.cs @@ -0,0 +1,19 @@ +namespace Qrakhen.Qamp.Core.Injector; + +/// +/// Class used to export custom .dll implementations as binary instruction files (*.sqi). +/// +public class Injector +{ + +} + +public enum Scope +{ + /// Within the global scope of the file that is importing this library + Global, + /// Within the scope of the module this library exports + Module, + /// An extension of the built-in type + Extension +} \ No newline at end of file diff --git a/Qrakhen.Qamp.Core/Qrakhen.Qamp.Core.csproj b/Qrakhen.Qamp.Core/Qrakhen.Qamp.Core.csproj index cdfe8fc..1130182 100644 --- a/Qrakhen.Qamp.Core/Qrakhen.Qamp.Core.csproj +++ b/Qrakhen.Qamp.Core/Qrakhen.Qamp.Core.csproj @@ -5,5 +5,6 @@ enable enable true + ..\Build\ diff --git a/Qrakhen.Qamp.Core/Tokenization/Dialect.cs b/Qrakhen.Qamp.Core/Tokenization/Dialect.cs index 59b7652..5caed20 100644 --- a/Qrakhen.Qamp.Core/Tokenization/Dialect.cs +++ b/Qrakhen.Qamp.Core/Tokenization/Dialect.cs @@ -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, " PreValues = new(); + public PushStack Outers = new(); public override string ToString() => "{}"; } \ No newline at end of file diff --git a/Qrakhen.Qamp.Core/Values/Objects/Function.cs b/Qrakhen.Qamp.Core/Values/Objects/Function.cs index 3b117de..2571709 100644 --- a/Qrakhen.Qamp.Core/Values/Objects/Function.cs +++ b/Qrakhen.Qamp.Core/Values/Objects/Function.cs @@ -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}()"; } \ No newline at end of file diff --git a/Qrakhen.Qamp.Core/Values/Objects/Instance.cs b/Qrakhen.Qamp.Core/Values/Objects/Instance.cs new file mode 100644 index 0000000..e78daa8 --- /dev/null +++ b/Qrakhen.Qamp.Core/Values/Objects/Instance.cs @@ -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(); +} diff --git a/Qrakhen.Qamp.Core/Values/Objects/Method.cs b/Qrakhen.Qamp.Core/Values/Objects/Method.cs new file mode 100644 index 0000000..553cd4a --- /dev/null +++ b/Qrakhen.Qamp.Core/Values/Objects/Method.cs @@ -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; +} diff --git a/Qrakhen.Qamp.Core/Values/Objects/Outer.cs b/Qrakhen.Qamp.Core/Values/Objects/Outer.cs new file mode 100644 index 0000000..0aa48b9 --- /dev/null +++ b/Qrakhen.Qamp.Core/Values/Objects/Outer.cs @@ -0,0 +1,40 @@ +using Qrakhen.Qamp.Core.Collections; + +namespace Qrakhen.Qamp.Core.Values.Objects; + +using T = ValueType; + +/// +/// Wrapper to access values that are spread around the stack inside contexts +/// +public class Outer(Pointer target) : Obj(T.Outer) +{ + private Value _stored = Value.Void; + public Pointer Target = target; + + public bool IsClosed { get; private set; } + + /// + /// Dynamic getter & setter that returns or writes to either the target value directly, + /// or the stored value if this has been closed. + /// + 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; + } +} diff --git a/Qrakhen.Qamp.Core/Values/Objects/Table.cs b/Qrakhen.Qamp.Core/Values/Objects/Table.cs new file mode 100644 index 0000000..a67db93 --- /dev/null +++ b/Qrakhen.Qamp.Core/Values/Objects/Table.cs @@ -0,0 +1,8 @@ +using Qrakhen.Qamp.Core.Collections; + +namespace Qrakhen.Qamp.Core.Values.Objects; + +public class Table : Register +{ + +} diff --git a/Qrakhen.Qamp.Core/Values/Ptr.cs b/Qrakhen.Qamp.Core/Values/Ptr.cs index ffb4e30..01cdca9 100644 --- a/Qrakhen.Qamp.Core/Values/Ptr.cs +++ b/Qrakhen.Qamp.Core/Values/Ptr.cs @@ -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 _register = new(0x10); + private static readonly PushStack _register = new(0x10); [Serialized] public readonly Address Address; @@ -31,5 +32,13 @@ public readonly struct Ptr Address = _register.Add(obj); } + public T? As(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}"; } \ No newline at end of file diff --git a/Qrakhen.Qamp.Core/Values/Value.cs b/Qrakhen.Qamp.Core/Values/Value.cs index f0c1edb..379a9b4 100644 --- a/Qrakhen.Qamp.Core/Values/Value.cs +++ b/Qrakhen.Qamp.Core/Values/Value.cs @@ -15,6 +15,8 @@ public interface IValue public readonly struct Value : IValue, IDebug { 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; @@ -28,9 +30,7 @@ public readonly struct Value : IValue, IDebug [FieldOffset(0x08)] [Serialized] public readonly T Type; [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) { } @@ -44,7 +44,20 @@ public readonly struct Value : IValue, IDebug public Value(Ref @ref) : this(T.Reference) => Ref = @ref; 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 private unsafe dynamic? AsDynamic(bool throwWhenNull = true) { - if (Type.HasFlag(T.Object)) + if (IsString) + return Ptr.As()!.Value; + + if (IsObj) return Ptr.Value; return Type switch { @@ -88,13 +104,18 @@ public readonly struct Value : IValue, IDebug 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) diff --git a/Qrakhen.Qamp.Core/Values/ValueOperation.cs b/Qrakhen.Qamp.Core/Values/ValueOperation.cs deleted file mode 100644 index 1bd3ef9..0000000 --- a/Qrakhen.Qamp.Core/Values/ValueOperation.cs +++ /dev/null @@ -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); - } -} \ No newline at end of file diff --git a/Qrakhen.Qamp.Core/Values/ValueType.cs b/Qrakhen.Qamp.Core/Values/ValueType.cs index 97090ba..2f0b51b 100644 --- a/Qrakhen.Qamp.Core/Values/ValueType.cs +++ b/Qrakhen.Qamp.Core/Values/ValueType.cs @@ -1,5 +1,9 @@ namespace Qrakhen.Qamp.Core.Values; +/// +/// todo: make value type a byte instead to save some memory on values +/// edit: if that's even possible with c#'s memory padding +/// [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 } \ No newline at end of file diff --git a/Qrakhen.Qamp.Digest/Class1.cs b/Qrakhen.Qamp.Digest/Class1.cs new file mode 100644 index 0000000..1a0484c --- /dev/null +++ b/Qrakhen.Qamp.Digest/Class1.cs @@ -0,0 +1,6 @@ +namespace Qrakhen.Qamp.Digest; + +public class Class1 +{ + +} diff --git a/Qrakhen.Qamp.Digest/Qrakhen.Qamp.Digest.csproj b/Qrakhen.Qamp.Digest/Qrakhen.Qamp.Digest.csproj new file mode 100644 index 0000000..359c961 --- /dev/null +++ b/Qrakhen.Qamp.Digest/Qrakhen.Qamp.Digest.csproj @@ -0,0 +1,14 @@ + + + + net10.0 + enable + enable + ..\Build\ + + + + + + + diff --git a/Qrakhen.Qamp.Runtime/Class1.cs b/Qrakhen.Qamp.Runtime/Class1.cs new file mode 100644 index 0000000..585c8e4 --- /dev/null +++ b/Qrakhen.Qamp.Runtime/Class1.cs @@ -0,0 +1,6 @@ +namespace Qrakhen.Qamp.Runtime; + +public class Class1 +{ + +} diff --git a/Qrakhen.Qamp.Runtime/Qrakhen.Qamp.Runtime.csproj b/Qrakhen.Qamp.Runtime/Qrakhen.Qamp.Runtime.csproj new file mode 100644 index 0000000..5c59036 --- /dev/null +++ b/Qrakhen.Qamp.Runtime/Qrakhen.Qamp.Runtime.csproj @@ -0,0 +1,15 @@ + + + + net10.0 + enable + enable + True + ..\Build\ + + + + + + + diff --git a/Qrakhen.Qamp.sln b/Qrakhen.Qamp.sln index b5d19c9..ce2534f 100644 --- a/Qrakhen.Qamp.sln +++ b/Qrakhen.Qamp.sln @@ -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 diff --git a/icon.png b/icon.png new file mode 100644 index 0000000..b622a75 Binary files /dev/null and b/icon.png differ