Compare commits

..

No commits in common. "dev" and "main" have entirely different histories.
dev ... main

143 changed files with 2 additions and 9347 deletions

View File

@ -1,5 +0,0 @@
root = true
[*.cs]
indent_style = space
indent_size = 3

10
.gitignore vendored
View File

@ -1,20 +1,14 @@
Debug/ Debug/
x86/
x64/
Release/ Release/
Build/
bin/ bin/
*/bin/
*/bin/*
obj/ obj/
log/ log/
lib/ lib/
.vs/ .vs/
*.nocmt.* *.nocmt.*
*.sqi
*.tmp
*.log
*.depend *.depend
*.stackdump *.stackdump
*.layout *.layout
*.user *.user
x86/
x64/

View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) [2026] [David Neumaier, Qrakhen]
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,6 +0,0 @@
public enum ConsoleCode
{
Error = -1,
OK = 0,
Exit = 1
}

View File

@ -1,27 +0,0 @@
namespace Qrakhen.Qamp.CLI;
[Flags]
public enum ConsoleMods
{
None = 0,
Red = 1,
Green = 2,
Blue = 4,
Yellow = Red | Green,
White = Red | Green | Blue,
Color = 1 << 10,
Bold = 1 << 20,
Italic = 1 << 21,
Underlined = 1 << 22,
Clear = 1 << 31,
}
public interface IConsolePainter
{
ConsoleMods Mods { get; set; }
}

View File

@ -1,10 +0,0 @@
public static class ConsoleExtensions
{
public static bool Check(this ConsoleKeyInfo keyInfo, ConsoleKey key, ConsoleModifiers modifier = default, bool exactModifier = false)
{
return ((exactModifier ?
keyInfo.Modifiers == modifier :
(keyInfo.Modifiers & modifier) == modifier)
&& keyInfo.Key == key);
}
}

View File

@ -1,57 +0,0 @@
using Qrakhen.Qamp.Core.Collections.Abstractions;
using Qrakhen.Qamp.Core.Tokenization;
using System.Text;
public class ConsoleRenderer
{
private readonly IReader<Token> _reader;
private readonly Stream _stream;
public ConsoleRenderer(Stream stream)
{
_stream = new MemoryStream();
stream.Position = 0;
stream.CopyTo(_stream);
_reader = new Reader(_stream);
}
public void Render((int x, int y) position)
{
Console.CursorVisible = false;
Console.SetCursorPosition(position.x, position.y);
List<Token> tokens = [];
while (!_reader.Done) {
tokens.Add(_reader.NextToken(true));
}
_stream.Position = 0;
foreach (var token in tokens) {
ConsoleColor color = ConsoleColor.Gray;
if (token.Type.IsBoolean())
color = ConsoleColor.Blue;
if (token.Type.IsString())
color = ConsoleColor.Cyan;
if (token.Type.IsNumber())
color = ConsoleColor.DarkCyan;
if (token.Type.IsOperator())
color = ConsoleColor.DarkGreen;
if (token.Type.IsBracket())
color = ConsoleColor.Red;
if (token.Type.IsIdentifier())
color = ConsoleColor.White;
if (token.Type.IsControl())
color = ConsoleColor.DarkYellow;
if (token.Type == TokenType.Error)
color = ConsoleColor.DarkRed;
Console.ForegroundColor = color;
byte[] buffer = new byte[token.Span.Length];
_stream.ReadExactly(buffer, 0, (int)token.Span.Length);
string str = Encoding.Default.GetString(buffer);
Console.Write(str);
Console.ForegroundColor = ConsoleColor.White;
if (token.Type == TokenType.NewLine)
Console.Write(" : ");
}
Console.ForegroundColor = ConsoleColor.White;
Console.CursorVisible = true;
}
}

View File

@ -1,142 +0,0 @@
using System.Text;
public class ConsoleWriter
{
private const string SYMBOL_INPUT = " <: ";
private const string SYMBOL_INPUT_CONTINUE = " : ";
private readonly List<char> _buffer = [];
private readonly Stack<char[]> _history = [];
private readonly Encoding _encoding;
private int _position;
private (int x, int y) _origin;
private (int x, int y) _cursorPosition => Console.GetCursorPosition();
private int _cursorOffset => SYMBOL_INPUT.Length;
private int _cursorLeft { get => Console.CursorLeft; set => Console.CursorLeft = value; }
private int _cursorTop { get => Console.CursorTop; set => Console.CursorTop = value; }
public bool UseSyntaxHighlighting { get; set; } = false;
public ConsoleWriter(Encoding encoding)
{
_encoding = encoding;
}
private void Write(char c) => Console.Write(c);
private void Write(string str) => Console.Write(str);
private void SetCursor((int x, int y) position) => Console.SetCursorPosition(position.x, position.y);
private void SetColor(ConsoleColor color, ConsoleColor background = ConsoleColor.Black)
{
Console.ForegroundColor = color;
Console.BackgroundColor = background;
}
private void Move(int x, int y = 0)
{
_position = Math.Min(_buffer.Count, Math.Max(0, _position + x));
_cursorLeft -= 1; //????
}
private void ClearLine()
{
int left = _cursorLeft;
while (_cursorLeft < Console.BufferWidth - 1)
Write(' ');
_cursorLeft = left;
}
private void Print((int x, int y) origin, (int x, int y)? remain = null)
{
SetColor(ConsoleColor.White);
SetCursor(origin);
Write(" <: ");
foreach (var c in _buffer) {
if (c == '\n') {
ClearLine();
Write("\n : ");
} else {
Write(c);
}
}
ClearLine();
if (remain.HasValue)
SetCursor(remain.Value);
}
private Stream Dispatch()
{
var stream = new MemoryStream();
var data = _buffer.ToArray();
stream.Write(_encoding.GetBytes(data));
_history.Push(data);
_buffer.Clear();
return stream;
}
public ConsoleCode Read(out Stream? stream)
{
stream = null;
ConsoleKeyInfo input;
_origin = _cursorPosition;
_position = 0;
Print(_origin);
do {
if (UseSyntaxHighlighting)
new ConsoleRenderer(new MemoryStream(_encoding.GetBytes(_buffer.ToArray()))).Render(_origin); // ~ so efficient ~ XD
Print(_origin, _cursorPosition);
input = Console.ReadKey(true);
if (input.Modifiers == ConsoleModifiers.Control) {
if (input.Key == ConsoleKey.C)
return ConsoleCode.Exit;
if (input.Key == ConsoleKey.H) {
UseSyntaxHighlighting = !UseSyntaxHighlighting;
Write($"\n #: {nameof(UseSyntaxHighlighting)} = {UseSyntaxHighlighting}\n <: ");
_origin = _cursorPosition;
}
if (input.Key == ConsoleKey.LeftArrow) {
}
continue;
}
if (input.Key == ConsoleKey.LeftArrow) {
if (_position > 0) {
_position--;
_cursorLeft--;
}
continue;
}
if (input.Key == ConsoleKey.RightArrow) {
if (_position < _buffer.Count) {
_position++;
_cursorLeft++;
}
continue;
}
if (input.Key == ConsoleKey.Backspace) {
if (_position > 0) {
_cursorLeft--;
_buffer.RemoveRange(--_position, 1);
}
continue;
} else if (input.Check(ConsoleKey.Enter, ConsoleModifiers.Shift)) {
_buffer.Insert(_position++, '\n');
_cursorLeft = _cursorOffset;
_cursorTop++;
continue;
} else {
_buffer.Insert(_position++, input.KeyChar);
_cursorLeft++;
}
} while (!input.Check(ConsoleKey.Enter, ConsoleModifiers.None, true));
stream = Dispatch();
return ConsoleCode.OK;
}
}

View File

@ -1,234 +0,0 @@
using Qrakhen.Qamp.Core;
using Qrakhen.Qamp.Core.Execution;
using Qrakhen.Qamp.Core.Logging;
using System.Drawing;
using System.Runtime.CompilerServices;
using System.Text;
List<char> Ignored = [ '\0', '\b' ];
Benchmark.Active = false;
LoggerService.Default = LogLevel.Error;
List<byte[]> History = [];
int historyCursor = -1;
ConsoleKeyInfo input;
bool useSyntaxHighlighting = false;
(int x, int y) cursor = (0, 0);
ConsoleCode code = ConsoleCode.Error;
Runner runner = new(new Options());
Stream stdOut = Console.OpenStandardOutput();
Stream stdErr = Console.OpenStandardError();
Stream stdIn = Console.OpenStandardInput();
void write(string what, Stream stream) => stream.Write(Encoding.Default.GetBytes(what));
string[] flags = args.Where(x => x.StartsWith("-")).Select(x => x[1..]).ToArray();
string[] files = args.Where(x => !x.StartsWith("-")).ToArray();
if (flags.IndexOf("h") > 0)
{
write(" :: Q& Help.\n", stdOut);
write(" Basic usage: qamp {FILES} {OPTIONS}\n", stdOut);
write(" Flags:\n", stdOut);
write(" -i --interactive \n", stdOut);
write(" -n --nurture \n", stdOut);
write(" -l --log-level=<int> \n", stdOut);
write(" -d --dialect=<sqr|default>\n", stdOut);
write(" Example: qamp ~/myNiceScript.qp -i -n -l=0\n", stdOut);
return (int)QRESULT.OK;
}
if (files.Length > 0)
{
foreach (var file in files)
{
if (File.Exists(file))
{
var result = runner.Run(File.OpenRead(file));
write($" :: {file}: {result.RunnerResult} ({result.Returned})\n", stdOut);
}
else
{
write($"Could not find file located at '{file}'.\n", stdErr);
return (int)QRESULT.NOT_FOUND;
}
}
}
if (flags.IndexOf("i") < 0 && files.Length > 0)
{
return (int)QRESULT.OK;
}
if (flags.IndexOf("i") < 0)
{
write(" :: Assuming interactive mode (-i).\n :: If that is intended, use qamp -i to prevent this notification.\n", stdOut);
}
do {
Console.ForegroundColor = ConsoleColor.White;
MemoryStream stream = new MemoryStream();
write(" <: ", stdOut);
cursor = Console.GetCursorPosition();
do {
if (useSyntaxHighlighting)
new ConsoleRenderer(stream).Render(cursor);
input = Console.ReadKey(true);
if (input.Modifiers == ConsoleModifiers.Control) {
if (input.Key == ConsoleKey.H) {
useSyntaxHighlighting = !useSyntaxHighlighting;
//write(Colors.ColorText($"\n #: {nameof(useSyntaxHighlighting)} = {useSyntaxHighlighting}\n <: ", 30));
write($"\n #: {nameof(useSyntaxHighlighting)} = {useSyntaxHighlighting}\n <: ", stdOut);
cursor = Console.GetCursorPosition();
}
if (input.Key == ConsoleKey.B) {
Benchmark.Active = !Benchmark.Active;
//write(Colors.ColorText($"\n #: {nameof(Benchmark)}.{nameof(Benchmark.Active)} = {Benchmark.Active}\n <: ", 30));
write($"\n #: {nameof(Benchmark)}.{nameof(Benchmark.Active)} = {Benchmark.Active}\n <: ", stdOut);
cursor = Console.GetCursorPosition();
}
if (input.Key == ConsoleKey.L) {
LoggerService.Default = LoggerService.Default < LogLevel.All ? LogLevel.All : LogLevel.Info;
//write(Colors.ColorText($"\n #: LogLevel = {LoggerService.Default}\n <: ", 30));
write($"\n #: LogLevel = {LoggerService.Default}\n <: ", stdOut);
cursor = Console.GetCursorPosition();
}
continue;
}
if (input.Key == ConsoleKey.LeftArrow) {
if (stream.Position > 0) {
stream.Position--;
Console.CursorLeft--;
}
continue;
}
if (input.Key == ConsoleKey.RightArrow) {
if (stream.Position < stream.Length) {
stream.Position++;
Console.CursorLeft++;
}
continue;
}
if (input.Key == ConsoleKey.UpArrow) {
historyCursor = Math.Min(History.Count - 1, historyCursor + 1);
byte[] bytes = History[historyCursor];
continue;
}
if (input.Key == ConsoleKey.DownArrow) {
historyCursor = Math.Max(0, historyCursor - 1);
byte[] bytes = History[historyCursor];
continue;
}
if (input.Key == ConsoleKey.Backspace && stream.Position > 0) {
Console.CursorLeft -= 1;
Console.Write(' ');
if (!useSyntaxHighlighting)
Console.CursorLeft -= 1;
//stream.Position--;
//stream.SetLength(stream.Position);
stream.Remove(stream.Position - 1, 1);
} else if (input.Key == ConsoleKey.Enter && input.Modifiers == ConsoleModifiers.Shift) {
if (!useSyntaxHighlighting)
Console.Write("\n : ");
stream.Insert(Encoding.Default.GetBytes(Environment.NewLine));
} else if (Ignored.IndexOf(input.KeyChar) < 0) {
if (!useSyntaxHighlighting)
Console.Write(input.KeyChar);
stream.Insert(Encoding.Default.GetBytes([input.KeyChar], 0, 1));
}
} while (input.Key != ConsoleKey.Enter || input.Modifiers == ConsoleModifiers.Shift);
try {
History.Insert(0, stream.GetBuffer());
Console.WriteLine();
runner.Run(stream);
stream.Dispose();
} catch (QampException e) {
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine($" !> {e.Message}");
if (e.StackTrace != null) {
Console.Write($" ! ");
Console.WriteLine(string.Join("\n ! ", e.StackTrace.Split('\n')));
}
} catch (Exception e) {
Console.ForegroundColor = ConsoleColor.DarkRed;
if (e.InnerException != null)
Console.WriteLine($" !? {e.InnerException.Message}");
Console.WriteLine($" !? {e.Message}");
if (e.StackTrace != null) {
Console.Write($" ! ");
Console.WriteLine(string.Join("\n ! ", e.StackTrace.Split('\n')));
}
}
} while (code != ConsoleCode.Exit);
return (int)QRESULT.OK;
// yes i'm making these up, just like anyone else
public enum QRESULT
{
OK = 0,
INVALID_ARGS = 0x14,
SYNTAX_ERROR = 0x72,
NOT_FOUND = 0x204
}
// leenux colors
public static class Colors
{
private readonly static int cBlack = 0,
cRed = 1,
cGreen = 2,
cYellow = 3,
cCyan = 4,
cPink = 5,
cCyanD = 6,
cWhite = 7;
private readonly static int fBack = 40,
fFore = 30;
private static string Wrap(int code) => @$"\e[{code}m";
// goote goute
public static string ColorText(string text, params int[] codes) =>
string.Join("", codes.Select(c => Wrap(c))) + text + Wrap(0);
public static string Red { get; } = Wrap(fFore + cRed);
}
public class Command
{
public class Parameters
{
}
public class Definition
{
public string Name { get; init; }
public string Alias { get; init; }
public class Parameter
{
public string Name { get; init; }
public string Alias { get; init; }
}
}
}
public class ArgumentAttribute(string name, string alias, string description)
{
public string Name { get; } = name;
public string Alias { get; } = alias;
public string Description { get; } = description;
}

View File

@ -1,8 +0,0 @@
{
"profiles": {
"Qrakhen.Qamp.CLI": {
"commandName": "Project",
"nativeDebugging": true
}
}
}

View File

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

View File

@ -1,21 +0,0 @@
# 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

@ -1,43 +0,0 @@

using Qrakhen.Qamp.Core;
using Qrakhen.Qamp.Core.Execution;
using Qrakhen.Qamp.Core.Tokenization;
using System.Text;
class C
{
void X()
{
ConsoleCode code = ConsoleCode.Exit;
Runner runner = new Runner(new Options());
ConsoleWriter cli = new ConsoleWriter(Encoding.Default);
do {
try {
code = cli.Read(out Stream? stream);
if (code == ConsoleCode.OK) {
if (stream == null)
throw new QampException($"Stream from CLI was null {code}");
Console.WriteLine();
runner.Run(stream);
}
if (code == ConsoleCode.Error)
throw new QampException($"CLI returned {code}");
} catch (QampException e) {
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine($" !> {e.Message}");
if (e.StackTrace != null) {
Console.Write($" ! ");
Console.WriteLine(string.Join("\n ! ", e.StackTrace.Split('\n')));
}
} catch (Exception e) {
Console.ForegroundColor = ConsoleColor.DarkRed;
Console.WriteLine($" !? {e.Message}");
if (e.StackTrace != null) {
Console.Write($" ! ");
Console.WriteLine(string.Join("\n ! ", e.StackTrace.Split('\n')));
}
}
} while (code != ConsoleCode.Exit);
}
}

View File

@ -1,24 +0,0 @@
using System.Text;
using Qrakhen.Qamp.Core.Execution;
namespace Qrakhen.Qamp.Core.Tests;
public class CodeTest
{
private readonly Runner _runner = new(new Options());
private RunnerResult ExecuteCode(string code)
{
using Stream stream = new MemoryStream();
stream.Write(Encoding.UTF8.GetBytes(code));
return _runner.Run(stream);
}
[Fact]
public void VariableDeclaration_Empty_AllocatesName()
{
string code = "var name;";
var result = ExecuteCode(code);
}
}

View File

@ -1,26 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<Platforms>AnyCPU;x64</Platforms>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.4" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../Qrakhen.Qamp.Core/Qrakhen.Qamp.Core.csproj" />
</ItemGroup>
<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>
</Project>

View File

@ -1,29 +0,0 @@
using System.Runtime.CompilerServices;
namespace Qrakhen.Qamp.Core.Abstractions;
public interface IDebug<out T>
{
T Debug(DebugLevel level = DebugLevel.None);
}
public interface IDebug<in TContext, out TOut>
{
TOut Debug(TContext? context, DebugLevel level = DebugLevel.None);
}
public enum DebugLevel
{
None = 0,
Mild = 1,
Strong = 2,
Verbose = 3
}
public static class Debugger
{
public static string GetContextString(object? origin, [CallerMemberName] string? caller = null)
{
return $"{{Debugger::Builder}}: [{origin?.ToString() ?? "null"}] <{origin?.GetType().Name ?? "null"}> ({caller ?? "null"})";
}
}

View File

@ -1,7 +0,0 @@
namespace Qrakhen.Qamp.Core.Abstractions;
public interface ISerialize<TSelf>
{
byte[] Serialize();
static abstract TSelf Deserialize(byte[] data);
}

View File

@ -1,55 +0,0 @@
using Qrakhen.Qamp.Core.Values;
using System.Runtime.CompilerServices;
using V = Qrakhen.Qamp.Core.Values.ValueType;
namespace Qrakhen.Qamp.Core;
public static class Assert
{
public static void IsPrimitive(params Value[] values)
{
AssertAndThrow(() => values.All(value => value.Is(V.Primitive, false)), values);
}
public static void NotZero(params Value[] values)
{
AssertAndThrow(() => values.All(value => value.Signed != 0), values);
}
public static void IsInteger(params Value[] values)
{
AssertAndThrow(() => values.All(value => value.Is(V.Integer, false)), values);
}
public static void NotVoid(params Value[] values)
{
AssertAndThrow(() => values.All(value => value.Type != V.Void), values);
}
private static void AssertAndThrow(Func<bool> assertion, object? parameter, [CallerMemberName] string? method = "") =>
AssertAndThrow(assertion, [parameter], method);
private static void AssertAndThrow(
Func<bool> assertion,
object?[] parameters,
[CallerMemberName] string? method = "")
{
if (!assertion.Invoke())
throw new AssertionException(method ?? "", parameters);
}
public class AssertionException : Exception
{
public readonly string Assertion;
public readonly object?[] Parameters;
public AssertionException(string assertion, params object?[] parameters)
: base($"Assertion {assertion} failed for parameters:{Environment.NewLine}" +
$"{string.Join(Environment.NewLine, parameters.Select(p => $" - <{p?.GetType().Name ?? "null"}>{p?.ToString() ?? "null"}"))}")
{
Assertion = assertion;
Parameters = parameters;
}
}
}

View File

@ -1,87 +0,0 @@
using Qrakhen.Qamp.Core.Logging;
using System.Diagnostics;
using System.Runtime.CompilerServices;
namespace Qrakhen.Qamp.Core;
public static class Benchmark
{
private static readonly Dictionary<string, Stopwatch> _clocks = new();
public static bool Active { get; set; }
private static readonly ILogger _logger = LoggerService.Get(nameof(Benchmark));
private static string MakeKey(string? id, string? member, string? file)
{
if (member == null)
return "global";
return $"{file?.Split('\\').Last() ?? "unknown"}{(id == null ? "" : $"<{id}>")}::{member}";
}
public static void Start(
string? message = null,
string? id = null,
[CallerMemberName] string? member = null,
[CallerFilePath] string? file = null,
[CallerLineNumber] int? line = null)
{
if (!Active)
return;
string key = MakeKey(id, member, file);
if (!_clocks.TryGetValue(key, out Stopwatch? sw)) {
sw = _clocks[key] = new Stopwatch();
IO.Console.Write($" ::: Registered new benchmark clock '{key}'");
}
if (!string.IsNullOrEmpty(message))
IO.Console.Write($" ::: {key}:{line} > {message}");
if (sw.IsRunning)
IO.Console.Write($" ::: {key}:{line} > already running with an elapsed time of {sw.Elapsed}. clock was reset.");
sw.Reset();
sw.Start();
}
public static long Report(
string? message = null,
string? id = null,
bool keep = true,
[CallerMemberName] string? member = null,
[CallerFilePath] string? file = null,
[CallerLineNumber] int? line = null)
{
if (!Active)
return 0;
string key = MakeKey(id, member, file);
if (!_clocks.TryGetValue(key, out Stopwatch sw)) {
_logger.Debug($"No clock found for '{key}', start one first using Benchmark.Start()");
return 0;
}
sw.Stop();
IO.Console.Write($" ::: {key}:{line} > {message ?? "Elapsed"}: {sw.Elapsed}");
if (keep)
sw.Start();
return sw.ElapsedMilliseconds;
}
public static long End(
string? message = null,
string? id = null,
[CallerMemberName] string? member = null,
[CallerFilePath] string? file = null,
[CallerLineNumber] int? line = null)
{
if (!Active)
return 0;
return Report(message, id, false, member, file, line);
}
}

View File

@ -1,8 +0,0 @@
using System.Numerics;
namespace Qrakhen.Qamp.Core.Collections.Abstractions;
public interface IAdd<out TKey, in TValue>
{
TKey Add(TValue value);
}

View File

@ -1,8 +0,0 @@
namespace Qrakhen.Qamp.Core.Collections.Abstractions;
public interface ICaptureBuffer<TData, THandle>
{
THandle BeginCapture();
TData[] EndCapture(THandle handle);
int Captured(THandle handle);
}

View File

@ -1,6 +0,0 @@
namespace Qrakhen.Qamp.Core.Collections.Abstractions;
public interface IConsumable<in T>
{
void Consume(T expected, string? message = null);
}

View File

@ -1,6 +0,0 @@
namespace Qrakhen.Qamp.Core.Collections.Abstractions;
public interface IGet<in TKey, out TValue>
{
TValue Get(TKey index);
}

View File

@ -1,3 +0,0 @@
namespace Qrakhen.Qamp.Core.Collections.Abstractions;
public interface IGetSet<in TKey, TValue> : IGet<TKey, TValue>, ISet<TKey, TValue>;

View File

@ -1,6 +0,0 @@
namespace Qrakhen.Qamp.Core.Collections.Abstractions;
public interface IPeekable<out T>
{
T Peek(int delta = -1);
}

View File

@ -1,7 +0,0 @@
namespace Qrakhen.Qamp.Core.Collections.Abstractions;
public interface IReadable<out T>
{
T Read(int position);
T[] Read(int position, int length);
}

View File

@ -1,10 +0,0 @@
using Qrakhen.Qamp.Core.Tokenization;
namespace Qrakhen.Qamp.Core.Collections.Abstractions;
public interface IReader<out T>
{
TokenPosition CurrentPosition { get; }
bool Done { get; }
T NextToken(bool includeCompilationIrrelevant = false);
}

View File

@ -1,7 +0,0 @@
namespace Qrakhen.Qamp.Core.Collections.Abstractions;
public interface ISeekable<TKey, out TValue>
{
TKey Position { get; }
TValue Seek(TKey position);
}

View File

@ -1,8 +0,0 @@
namespace Qrakhen.Qamp.Core.Collections.Abstractions;
public interface ISet<in TKey, in TValue>
{
TValue this[TKey index] { set; }
void Set(TKey index, TValue value);
}

View File

@ -1,13 +0,0 @@
namespace Qrakhen.Qamp.Core.Collections.Abstractions;
public interface IStack<out TKey, TValue> : IPush<TKey, TValue>, IPop<TValue>;
public interface IPush<out TKey, in T>
{
TKey Push(T value);
}
public interface IPop<out T>
{
T Pop();
}

View File

@ -1,7 +0,0 @@
namespace Qrakhen.Qamp.Core.Collections.Abstractions;
public interface ISteppable<out T>
{
bool Done { get; }
T Next();
}

View File

@ -1,6 +0,0 @@
namespace Qrakhen.Qamp.Core.Collections.Abstractions;
public interface IToArray<out T>
{
T[] ToArray();
}

View File

@ -1,73 +0,0 @@
using Qrakhen.Qamp.Core.Collections.Abstractions;
using System.Collections;
namespace Qrakhen.Qamp.Core.Collections;
/// <summary>
/// Expanding Collection based on doubling capacity allocations when adding elements.
/// </summary>
/// <remarks>
/// Not implementing <see cref="IList{T}"/> because it expects <see cref="Add"> to return void,
/// and we don't need that here in Q&.
/// </remarks>
/// <typeparam name="T"></typeparam>
public abstract class Expander<T> :
IEnumerable<T>,
IGetSet<long, T>,
IAdd<long, T>
{
protected T[] Data;
public long Count { get; protected set; } = 0;
public long Capacity => Data.Length;
protected Expander(int capacity = 0x10)
{
Data = new T[capacity];
}
protected Expander(IEnumerable<T> data)
{
Data = data.ToArray();
Count = Data.Length;
}
public T this[long index] {
get => Get(index);
set => Set(index, value);
}
public T Get(long index) => Data[index];
public void Set(long index, T value)
{
Prepare(index);
Data[index] = value;
}
public long Add(T value)
{
Prepare(Count);
Data[Count] = value;
return Count++;
}
protected void Prepare(long position)
{
while (position >= Data.Length) {
T[] grown = new T[Data.Length * 2];
Array.Copy(Data, grown, Count);
Data = grown;
}
}
public IEnumerator<T> GetEnumerator()
{
foreach (T? item in Data) {
yield return item;
}
}
IEnumerator IEnumerable.GetEnumerator()
=> GetEnumerator();
}

View File

@ -1,41 +0,0 @@
using Qrakhen.Qamp.Core.Collections.Abstractions;
using System.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>
{
protected T[] Data;
public long Length => Data.LongLength;
public T this[long index] {
get => Get(index);
set => Set(index, value);
}
public FixedArray(IEnumerable<T> data)
{
Data = data.ToArray();
}
public T Get(long index) => Data[index];
public void Set(long index, T value)
{
Data[index] = value;
}
public IEnumerator<T> GetEnumerator()
{
foreach (var item in Data)
yield return item;
}
IEnumerator IEnumerable.GetEnumerator()
=> GetEnumerator();
}

View File

@ -1,58 +0,0 @@
using Qrakhen.Qamp.Core.Collections.Abstractions;
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 Cursor;
public T Current {
get => Get();
set => Set(value);
}
public Pointer(IGetSet<long, T> target, long pointer = 0)
{
Target = target;
Cursor = pointer;
}
public Pointer<T> Branch(long delta = 0)
{
return new Pointer<T>(Target, Cursor + delta);
}
/// <summary>
/// Returns the next item from <see cref="Target"/> and increases the pointer by 1.
/// </summary>
public T Next() => Target.Get(Cursor++);
/// <summary>
/// Sets the value of <see cref="Target"/> RELATIVE from the current position (<paramref name="delta"/>).
/// </summary>
public void Set(long delta, T value) => Target.Set(Cursor + delta, value);
/// <summary>
/// Sets the value of <see cref="Target"/> at the current <see cref="Cursor"/> location.
/// </summary>
public void Set(T value) => Set(Cursor, value);
/// <summary>
/// Gets the value of <see cref="Target"/> RELATIVE from the current position (<paramref name="delta"/>).
/// </summary>
public T Get(long delta) => Target.Get(Cursor + delta);
/// <summary>
/// Gets the value of <see cref="Target"/> at the current <see cref="Cursor"/> location.
/// </summary>
public T Get() => Get(Cursor);
public override string ToString()
{
return $"&{Cursor:X4}={Current}";
}
}

View File

@ -1,43 +0,0 @@
using Qrakhen.Qamp.Core.Collections.Abstractions;
using System.Collections;
namespace Qrakhen.Qamp.Core.Collections;
/// <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 int _position;
public int Position => _position;
public int Length => _data.Length;
public bool Done => _position >= _data.Length;
public PopStack(IEnumerable<T> data)
{
_data = data.ToArray();
_position = 0;
}
public T Pop() => _data[_position++];
public T Peek(int delta = 0) => _data[_position + delta];
public bool CanPeek(int delta = 0)
=> _position + delta > -1 && _position + delta < _data.Length;
public IEnumerator<T> GetEnumerator()
{
foreach (var item in _data)
yield return item;
}
IEnumerator IEnumerable.GetEnumerator()
=> GetEnumerator();
}

View File

@ -1,60 +0,0 @@
using Qrakhen.Qamp.Core.Collections.Abstractions;
namespace Qrakhen.Qamp.Core.Collections;
/// <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 PushStack(int initialCapacity = 0x10)
{
Data = new T[initialCapacity];
}
public T? Find(Func<T, bool> callback)
{
for (int i = 0; i < Count; i++)
if (callback(Data[i]))
return Data[i];
return default;
}
public void Push(T[] array)
{
foreach (T value in array) {
Add(value);
}
}
public void Push(IEnumerable<T> array)
{
foreach (T value in array) {
Add(value);
}
}
public long Push(T value) => Add(value);
public bool TryGet(long index, out T value)
{
value = default;
if (index < 0 || index >= Count)
return false;
value = Data[index];
return true;
}
public T[] ToArray() => Data.Subset(0, Count);
public T[] Subset(long from, int length)
{
T[] result = new T[length];
Array.Copy(Data, from, result, 0, length);
return result;
}
}

View File

@ -1,48 +0,0 @@
using Qrakhen.Qamp.Core.Collections.Abstractions;
using System.Collections;
using System.Diagnostics.CodeAnalysis;
namespace Qrakhen.Qamp.Core.Collections;
/// <summary>
/// Dictionary wrapper implementing <see cref="IGetSet{TKey, TValue}"/>.
/// </summary>
public class Register<TKey, TValue> :
IGetSet<TKey, TValue>,
IToArray<TValue>,
IEnumerable<KeyValuePair<TKey, TValue>>
where TKey : notnull
{
private readonly Dictionary<TKey, TValue> _data = new();
public int Length => _data.Count;
public TValue this[TKey key] {
get => Get(key);
set => Set(key, value);
}
public TKey Add(TKey key, TValue value)
{
_data.Add(key, value);
return key;
}
public bool TryGet(TKey key, [MaybeNullWhen(false)] out TValue value) => _data.TryGetValue(key, out value);
public TValue Get(TKey key) => _data[key];
public void Set(TKey key, TValue value) => _data[key] = value;
public bool Has(TKey key) => _data.ContainsKey(key);
public TValue[] ToArray() => _data.Values.ToArray();
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
foreach (var item in _data)
yield return item;
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}

View File

@ -1,59 +0,0 @@
using Qrakhen.Qamp.Core.Collections.Abstractions;
namespace Qrakhen.Qamp.Core.Collections;
/// <summary>
/// Stack-like implementation with a few added features like seeking and setting values.
/// </summary>
/// <typeparam name="T"></typeparam>
public class StackLike<T> : Expander<T>,
IPush<long, T>,
IPop<T>,
ISeekable<long, T>,
IPeekable<T>,
IToArray<T>
{
public long Position => Count;
public T this[int position] {
get => Data[position];
set => Data[position] = value;
}
public StackLike(int capacity = 0x10)
{
Data = new T[capacity];
Count = 0;
}
public StackLike(IEnumerable<T> data)
{
Data = data.ToArray();
Count = 0;
}
public long Push(T value) => Add(value);
public T Pop() => Data[--Count];
public T Seek(long position)
{
Count = position;
return this[Count];
}
public T Peek(int delta = -1) => Data[Count + delta];
public bool CanPeek(int delta = 0)
=> Count + delta > -1 && Count + delta < Count;
/// <summary>
/// Decimates this stack down to the given position, overwriting everything beyond that when pushing in the future.
/// </summary>
public void Decimate(long position)
{
Count = position;
}
public T[] ToArray() => Data.ToArray();
}

View File

@ -1,184 +0,0 @@
using Qrakhen.Qamp.Core.Collections.Abstractions;
using Qrakhen.Qamp.Core.Values;
using System;
using System.Collections;
using System.Diagnostics.CodeAnalysis;
namespace Qrakhen.Qamp.Core.Collections;
/// <summary>
/// Similar to a register with <see cref="Address"/> as key;
/// But with the caveat that the memory slots can be allocated or freed up,
/// automatically seeking for free addresses when adding data,
/// making it much easier to deal with addresses or pointers.
/// </summary>
public class Table<TValue> :
IGetSet<Address, TValue>,
IToArray<TValue>,
IEnumerable<KeyValuePair<Address, TValue>>
{
public const int BLOCK_SIZE = sizeof(int) * 8;
private const uint BLOCK_FULL = (uint)((1UL << 32) - 1);
private readonly TValue[] _data;
private readonly uint[] _alloc;
public int Size { get; }
public int Blocks => Size / BLOCK_SIZE;
public TValue this[Address index]
{
get => Get(index);
set => Set(index, value);
}
public Table(int size = 4096)
{
Size = size;
if (size % BLOCK_SIZE != 0)
throw new ArgumentException($"{nameof(Table<TValue>)} size parameter must be product (multiple) of its block size {BLOCK_SIZE}.");
_data = new TValue[size];
_alloc = new uint[size / BLOCK_SIZE];
}
private int GetBlockIndex(Address address)
{
return (int)(address / BLOCK_SIZE);
}
private int GetBitIndex(Address address)
{
return (int)(address % BLOCK_SIZE);
}
public bool IsAllocated(Address address)
{
return (_alloc[GetBlockIndex(address)] & (1 << GetBitIndex(address))) > 0;
}
private void SetAllocBit(Address address, bool value)
{
int block = GetBlockIndex(address);
int index = GetBitIndex(address);
if (value)
_alloc[block] |= 1U << index;
else
_alloc[block] &= ~(1U << index);
}
private Address SeekFree(Address from = default)
{
Address cur = from - (from % BLOCK_SIZE);
while (cur < Size)
{
int block = GetBlockIndex(cur);
uint mask = _alloc[block];
if (mask < BLOCK_FULL)
{
int index = 0;
while ((mask & (1 << index)) > 0)
index++;
return (Address)((long)block * BLOCK_SIZE + index);
}
cur += BLOCK_SIZE;
}
return Address.Void;
}
private bool Validate(Address address)
{
if (address < 0 || address >= Size)
throw new RuntimeException($"Tried to access address outside memory {address}");
return true;
}
public void Free(Address address, int size = 1)
{
ArgumentOutOfRangeException.ThrowIfNotEqual(1, size); // only support single address freeing atm
SetAllocBit(address, false);
}
public Address Allocate(int size = 1)
{
ArgumentOutOfRangeException.ThrowIfNotEqual(1, size); // only support single address allocation atm
Address address = SeekFree();
SetAllocBit(address, true);
return address;
}
public TValue[] ToArray()
{
return [.. _data];
}
public TValue Get(Address index)
{
Validate(index);
if (!IsAllocated(index))
return default;
return _data[index];
}
public bool TryGet(Address index, [MaybeNullWhen(false)] out TValue? value)
{
Validate(index);
value = default;
if (!IsAllocated(index))
return false;
value = _data[index];
return true;
}
public void Set(Address index, TValue value)
{
Validate(index);
SetAllocBit(index, true);
_data[index] = value;
}
/// <summary>
/// Calls <see cref="Allocate(int)"/> and then <see cref="Set(Address, TValue)"/>,
/// returning the address at which <paramref name="value"/> was stored at.
/// </summary>
public Address Add(TValue value)
{
Address free = SeekFree();
if (free == Address.Void)
throw new QampException($"Tried to add {value} to a memory block, but no free memory could be allocated.");
Set(free, value);
return free;
}
public string Print()
{
string r = $"Table [{nameof(TValue)}]:\n";
int c = 0;
for (int b = 0; b < Blocks; b++)
{
uint block = _alloc[b];
if (block == 0)
continue;
for (int i = 0; i < BLOCK_SIZE; i++)
{
Address address = MakeAddress(b, i);
if (!IsAllocated(address))
continue;
c++;
r += $" {address}: {Get(address)}\n";
}
}
r += $"{c}/{Size - c} slots allocated.";
return r;
}
public static Address MakeAddress(int block, int index) => new((long)block * BLOCK_SIZE + index);
public IEnumerator<KeyValuePair<Address, TValue>> GetEnumerator()
{
for (Address addr = 0; addr < Size; addr++)
yield return new KeyValuePair<Address, TValue>(addr, Get(addr));
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

View File

@ -1,20 +0,0 @@
using Qrakhen.Qamp.Core.Collections;
namespace Qrakhen.Qamp.Core.Compilation.Builders;
public class Builder
{
public Builder? Outer { get; init; }
public FunctionBuilder Function = new();
public readonly FunctionType Type;
public StackLike<Local> Locals = [];
public StackLike<Outer> Outers = [];
public int ScopeDepth = 0;
public Builder(FunctionType type = FunctionType.Code, Builder? outer = null)
{
Type = type;
Outer = outer;
}
}

View File

@ -1,9 +0,0 @@
namespace Qrakhen.Qamp.Core.Compilation.Builders;
public class ClassBuilder
{
public string Name;
public ClassBuilder? Outer;
public bool IsDerived;
}

View File

@ -1,18 +0,0 @@
using Qrakhen.Qamp.Core.Values.Objects;
namespace Qrakhen.Qamp.Core.Compilation.Builders;
public class FunctionBuilder
{
public SegmentBuilder Segment = new();
public string Name;
public int OuterCount;
public int ArgumentCount;
public long CurrentInstruction => Segment.Instructions.Count;
public Function Build()
{
return new Function(Name, Segment.Build(), OuterCount, ArgumentCount);
}
}

View File

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

View File

@ -1,39 +0,0 @@
using Qrakhen.Qamp.Core.Abstractions;
using Qrakhen.Qamp.Core.Collections;
using Qrakhen.Qamp.Core.Execution;
using Qrakhen.Qamp.Core.Values;
namespace Qrakhen.Qamp.Core.Compilation.Builders;
public class SegmentBuilder() : IBuilder<Segment>, IDebug<string>
{
public readonly PushStack<Instruction> Instructions = new();
public readonly PushStack<Value> Constants = new();
public Segment Build()
{
return new Segment(Instructions.ToArray(), Constants.ToArray());
}
public string Debug(DebugLevel level = DebugLevel.None)
{
string str = Debugger.GetContextString(this);
for (int i = 0; i < Instructions.Count; i++) {
var instruction = Instructions[i];
/*if ((instruction.Code & 0x80) == 1) {
// dynamic
int length = instruction.Code & (0x80 - 1);
byte[] index = Instructions.Subset(i + 1, length).Select(n => n.Code).ToArray();
str += $"\n [{i:x4}]: Dynamic ({string.Join(' ', index)})";
i += length;
} else*/
str += $"\n [{i:x4}]: {instruction}".PadRight(32);
/*if (instruction.OpCode == OpCode.Constant) {
i++; // i know, i know
str += $" : {Constants[Instructions.Subset(i, 8).Select(n => n.Code).ToArray().ToInt64()]}";
i += 8;
}*/
}
return str;
}
}

View File

@ -1,895 +0,0 @@
using Qrakhen.Qamp.Core.Collections;
using Qrakhen.Qamp.Core.Execution;
using Qrakhen.Qamp.Core.Values;
using T = Qrakhen.Qamp.Core.Tokenization.TokenType;
using Op = Qrakhen.Qamp.Core.Execution.OpCode;
using String = Qrakhen.Qamp.Core.Values.Objects.String;
using Qrakhen.Qamp.Core.Compilation.Builders;
using Qrakhen.Qamp.Core.Values.Objects;
using Qrakhen.Qamp.Core.Tokenization;
using Qrakhen.Qamp.Core.Logging;
using Qrakhen.Qamp.Core.Collections.Abstractions;
namespace Qrakhen.Qamp.Core.Compilation;
public record struct Local(string Name, int Depth = -1, bool IsCaptured = false);
public record struct Outer(long Index, bool IsLocal);
public enum FunctionType { Function, Constructor, Method, Code }
internal class CompilerState
{
public readonly IReader<Token> Reader;
public Token Current;
public Token Previous;
public TokenPosition ErrorPosition = TokenPosition.None;
public TokenPosition CurrentPosition => Reader.CurrentPosition;
public bool HadError => !ErrorPosition.IsNone;
public bool Done => Reader.Done;
public bool ThrowOnError { get; set; } = true;
public Builder Builder;
public FunctionBuilder Function => Builder.Function;
public long CurrentInstruction => Function.Segment.Instructions.Count;
}
public class DigesterProvider
{
public object ExpressionDigester; // etc.
}
public abstract class xDigester
{
}
public class Digester : ISteppable<Token>
{
private readonly CompilerState _compiler;
public event Action<Token, string>? Error;
private readonly ILogger _logger = LoggerService.Get<Digester>();
private readonly IReader<Token> _reader;
internal FunctionBuilder Function => Builder.Function;
internal Token Current;
internal Token Previous;
public Builder Builder { get; private set; }
public ClassBuilder? ClassBuilder { get; private set; }
public bool ThrowOnError { get; set; } = true;
public bool HadError => !ErrorPosition.IsNone;
public bool Done => _reader.Done;
public long CurrentInstruction => Function.Segment.Instructions.Count;
public TokenPosition CurrentPosition => _reader.CurrentPosition;
public TokenPosition ErrorPosition { get; private set; } = TokenPosition.None;
public Digester(IReader<Token> reader)
{
Builder = CreateBuilder(FunctionType.Code);
_reader = reader;
}
public Function Digest()
{
Benchmark.Start($"Digest begin");
#if LOG
_logger.Method();
#endif
TokenPosition previousPosition = _reader.CurrentPosition;
Next();
while (!Done && !Match(T.Eof)) {
Token token = Current;
#if LOG
_logger.Verbose($"Digesting {token}");
#endif
if (previousPosition == _reader.CurrentPosition) {
ErrorAtCurrent("Loop detected / could not digest further");
break;
}
Process();
}
SegmentBuilder _debug = Builder.Function.Segment;
Function function = FinishBuilder();
#if LOG
_logger.Debug($"Digest done, instruction set:");
#endif
#if LOG
_logger.Verbose(_debug.Debug());
#endif
Benchmark.End($"Digest end");
return function;
}
internal Builder CreateBuilder(FunctionType type)
{
Builder builder = new(type, Builder);
if (type != FunctionType.Code)
builder.Function.Name = Previous.Value!;
builder.Locals.Push(new Local(type == FunctionType.Function ? "" : "this", 0));
Builder = builder;
return Builder;
}
internal Function FinishBuilder()
{
EmitReturn();
Function function = Builder.Function.Build();
Builder = Builder.Outer;
return function;
}
internal void Process()
{
#if LOG
_logger.Method();
#endif
if (Match(T.Class))
DeclareClass();
else if (Match(T.Function))
DeclareFunction();
else if (Match(T.Var))
DeclareVariable();
else
Statement();
}
internal void Statement()
{
#if LOG
_logger.Method();
#endif
if (Match(T.If))
If();
else if (Match(T.Else))
Else();
else if (Match(T.While))
While();
else if (Match(T.Do))
Do();
else if (Match(T.For))
For();
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
Expression();
}
internal void Expression()
{
#if LOG
_logger.Method();
#endif
ParseExpression();
if (Builder.Type == FunctionType.Code && Check(T.Eof))
Emit(Op.Print); // if we got presented with only an expression on top-level code and no ; at the end,
// we assume the user wants to see the value.
else {
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.
// not pushing it, e.g. with a flag to the Expression() call, would be a good todo idea.
//
// a tad bit older me: it's a terrible idea as return a = b; wouldn't work anymore
}
}
internal void ParseExpression() => WeightedDigest(Weight.Assign);
internal void Context()
{
#if LOG
_logger.Method();
#endif
BeginScope();
Block();
EndScope();
}
internal void Block()
{
#if LOG
_logger.Method();
#endif
while (!Check(T.ContextClose) && !Check(T.Eof))
Process();
Consume(T.ContextClose, "Expected '}' after context");
}
internal void WeightedDigest(Weight precedence)
{
#if LOG
_logger.Method();
#endif
Next();
Rule rule = ExpressionParser.Get(Previous.Type);
if (rule.Prefix == null) {
ErrorAtCurrent("Expected expression");
return;
}
bool canAssign = precedence <= Weight.Assign;
rule.Prefix(this, canAssign);
while (precedence <= ExpressionParser.Get(Current.Type).Weight) {
Next();
ExpressionParser.Get(Previous.Type).Infix?.Invoke(this, canAssign);
}
if (canAssign && Match(T.Equal))
ErrorAtCurrent("Invalid assignment target.");
}
internal void DeclareClass()
{
#if LOG
_logger.Method();
#endif
Consume(T.Identifier, "Expected a class name.");
// we need both a global name constant and a local declaration for this class
string name = Previous.Value!;
long identifier = IdentifierConstant(name);
DeclareLocal(name);
EmitDynamic(Op.Class, identifier);
DefineVariable(identifier);
ClassBuilder = new ClassBuilder {
Outer = ClassBuilder,
Name = name,
IsDerived = false
};
if (Match(T.Colon)) {
Consume(T.Identifier, "Expected base class name.", false);
ExpressionParser.Variable(this, false);
if (name == Previous.Value!)
ErrorAtPrevious($"Class {name} can not inherit from itself!");
// Add base reference by injecting a synthetic base variable into a virtual scope
BeginScope();
AddLocal("base");
DefineVariable(0);
ExpressionParser.Variable(this, name, false);
ClassBuilder.IsDerived = true;
}
ExpressionParser.Variable(this, name, false);
Consume(T.ContextOpen, "Expected '{' for class body declaration.", false);
while (!Check(T.ContextClose) && !Check(T.Eof))
DeclareMember();
Consume(T.ContextClose, "Expected '}' to end class body declaration.", false);
Emit(Op.Pop);
if (ClassBuilder.IsDerived)
EndScope();
ClassBuilder = ClassBuilder.Outer;
}
internal void DeclareFunction()
{
#if LOG
_logger.Method();
#endif
long global = ParseVariable();
InitializeLocal();
CreateFunction(FunctionType.Function);
DefineVariable(global);
}
internal void DeclareMember()
{
// todo: only methods allowed atm, add fields and shit too
#if LOG
_logger.Method();
#endif
Consume(T.Identifier, "Expected method name.", false);
long identifier = IdentifierConstant(Previous.Value!);
FunctionType type = FunctionType.Method;
if (Previous.Value == ClassBuilder?.Name)
type = FunctionType.Constructor;
CreateFunction(type);
EmitDynamic(Op.Method, identifier);
}
internal void DeclareVariable()
{
#if LOG
_logger.Method();
#endif
long global = ParseVariable();
if (Match(T.Equal))
ParseExpression();
else
Emit(Op.Null);
Consume(T.Semicolon, "missing ; after variable declaration");
DefineVariable(global);
}
internal void Export()
{
#if LOG
_logger.Method();
#endif
throw new NotImplementedException($"Import is not yet implemented. Sorry, it's difficult.");
}
internal void Import()
{
#if LOG
_logger.Method();
#endif
throw new NotImplementedException($"Import is not yet implemented. Sorry, it's difficult.");
}
internal void If()
{
#if LOG
_logger.Method();
#endif
Consume(T.GroupOpen, "Expected '(' after if");
ParseExpression();
Consume(T.GroupClose, "Expected ')' after condition");
long then = EmitJump(Op.JumpIfFalse);
Emit(Op.Pop);
Statement();
long @else = EmitJump(Op.Jump);
PatchJump(then);
Emit(Op.Pop);
if (Match(T.Else))
Statement();
PatchJump(@else);
}
internal void Else()
{
#if LOG
_logger.Method();
#endif
ErrorAtPrevious("This 'else' is very alone :( Have you meant to place it after an 'if'?");
}
internal void While()
{
#if LOG
_logger.Method();
#endif
long start = Function.CurrentInstruction;
Consume(T.GroupOpen, "Expected while condition to be placed inside parentheses. Missing '('.", false);
ParseExpression();
Consume(T.GroupClose, "Expected while condition to be placed inside parentheses. Missing ')'.", false);
long exit = EmitJump(Op.JumpIfFalse);
Statement();
EmitLoop(start);
PatchJump(exit);
Emit(Op.Pop);
}
internal void Do()
{
#if LOG
_logger.Method();
#endif
// todo: just do a while loop here with synthetic counter variables checked at end
throw new NotImplementedException();
}
internal void For()
{
#if LOG
_logger.Method();
#endif
BeginScope();
Consume(T.GroupOpen, "Expected '(' after for statement");
if (Match(T.Semicolon)) {
// nothing
} else if (Match(T.Var)) {
DeclareVariable();
} else {
Expression();
}
long start = Function.CurrentInstruction;
long exit = -1;
if (!Match(T.Semicolon)) {
ParseExpression();
Consume(T.Semicolon, "Expected ';' after condition");
exit = EmitJump(Op.JumpIfFalse);
Emit(Op.Pop);
}
if (!Match(T.GroupClose)) {
long body = EmitJump(Op.Jump);
long increment = Function.Segment.Instructions.Count;
ParseExpression();
Emit(Op.Pop);
Consume(T.GroupClose, "Expected ')' after for clause");
EmitLoop(start);
start = increment;
PatchJump(body);
}
Statement();
EmitLoop(start);
if (exit > -1) {
PatchJump(exit);
Emit(Op.Pop);
}
EndScope();
}
internal void Return()
{
#if LOG
_logger.Method();
#endif
if (Builder.Type == FunctionType.Code) {
ErrorAtPrevious($"Interesting approach, but that won't work.");
return;
}
if (Match(T.Semicolon)) {
EmitReturn();
} else {
if (Builder.Type == FunctionType.Constructor) {
ErrorAtPrevious($"We're not returning from a constructor where I'm from.");
return;
}
ParseExpression();
Consume(T.Semicolon, $"Expected ';' after return.");
Emit(Op.Return);
}
}
internal void Print()
{
#if LOG
_logger.Method();
#endif
ParseExpression();
Consume(T.Semicolon, "Expected ';' after print call.");
Emit(Op.Print);
}
internal void PrintGlobals()
{
#if LOG
_logger.Method();
#endif
Consume(T.Semicolon, "Expected ';' after print call.");
Emit(Op.PrintGlobals);
}
internal void PrintStack()
{
#if LOG
_logger.Method();
#endif
Consume(T.Semicolon, "Expected ';' after print call.");
Emit(Op.PrintStack);
}
internal void PrintExpr()
{
#if LOG
_logger.Method();
#endif
Consume(T.Semicolon, "Expected ';' after print call.");
Emit(Op.PrintExpr);
}
internal void TypeOf()
{
#if LOG
_logger.Method();
#endif
ParseExpression();
Emit(Op.Typeof);
}
internal void BeginScope()
{
#if LOG
_logger.Method();
#endif
Builder.ScopeDepth++;
}
internal void EndScope()
{
#if LOG
_logger.Method();
#endif
Builder.ScopeDepth--;
while (Builder.Locals.Count > 0 &&
Builder.Locals.Peek().Depth > Builder.ScopeDepth) {
if (Builder.Locals.Peek().IsCaptured) {
Emit(Op.CloseOuter);
} else {
Emit(Op.Pop);
}
Builder.Locals.Pop();
}
}
internal void CreateFunction(FunctionType type)
{
#if LOG
_logger.Method();
#endif
Builder next = CreateBuilder(type);
BeginScope();
Consume(T.GroupOpen, "Expected '(' to start argument list.");
if (!Check(T.GroupClose)) {
do {
Builder.Function.ArgumentCount++;
if (Builder.Function.ArgumentCount > 0xFF)
ErrorAtCurrent($"In the name of the lord, how many arguments do you need?");
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.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);
EmitDynamic(outer.Index);
}
}
internal long ResolveLocal(Builder builder, string name)
{
for (int i = 0; i < builder.Locals.Count; i++) {
if (builder.Locals[i].Name == name)
return i;
}
return -1;
}
internal void AddLocal(string? name)
{
#if LOG
_logger.Method(name);
#endif
Builder.Locals.Push(new Local(ThrowIfEmpty(name), -1));
}
internal void DeclareLocal(string? name)
{
if (Builder.ScopeDepth == 0)
return;
#if LOG
_logger.Method(name);
#endif
for (int i = 0; i < Builder.Locals.Count; i++) {
Local local = Builder.Locals[i];
// skip if the most recent local isn't even in our scope
if (local.Depth != -1 && local.Depth < Builder.ScopeDepth)
break;
if (ThrowIfEmpty(name).Equals(local.Name))
ErrorAtCurrent($"A variable by the name {name} already exists in the current scope.");
}
AddLocal(name);
}
internal long AddOuter(Builder builder, long index, bool isLocal)
{
for (int i = 0; i < builder.Function.OuterCount; i++) {
Outer outer = builder.Outers[i];
if (outer.Index == index && outer.IsLocal == isLocal)
return i;
}
builder.Outers.Push(new Outer(index, isLocal));
return builder.Outers.Count - 1;
}
internal long ResolveOuter(Builder builder, string name)
{
if (builder.Outer == null)
return -1;
long local = ResolveLocal(builder.Outer, name);
if (local > -1) {
Local element = builder.Outer.Locals.Get(local);
element.IsCaptured = true;
builder.Outer.Locals.Set(local, element);
return AddOuter(builder, local, true);
}
long outer = ResolveOuter(builder.Outer, name);
if (outer > -1)
return AddOuter(builder, outer, false);
return -1;
}
internal long IdentifierConstant(string? name)
{
#if LOG
_logger.Method(name);
#endif
return MakeConstant(String.Make(name ??
throw new TokenException("Empty string value for identifier detected", _reader, Current)));
}
internal long MakeConstant(Value value)
{
#if LOG
_logger.Method(value.ToString());
#endif
long constant = Function.Segment.Constants.Add(value);
#if LOG
_logger.Verbose($"Registered constant {value} at index {constant}");
#endif
return constant;
}
internal long ParseVariable()
{
#if LOG
_logger.Method();
#endif
string? name = Consume(T.Identifier, "Missing identifier for variable").Value;
if (Builder.ScopeDepth == 0)
return IdentifierConstant(name);
DeclareLocal(name);
return 0;
}
internal void InitializeLocal()
{
if (Builder.ScopeDepth == 0)
return;
#if LOG
_logger.Method();
#endif
var local = Builder.Locals.Peek();
local.Depth = Builder.ScopeDepth;
Builder.Locals.Set(Builder.Locals.Position - 1, local);
}
internal void DefineVariable(long index)
{
#if LOG
_logger.Method(index.ToString());
#endif
if (Builder.ScopeDepth > 0) {
InitializeLocal();
} else {
Emit(Op.DefineGlobal);
EmitDynamic(index);
}
}
internal byte Arguments()
{
byte count = 0;
if (!Check(T.GroupClose)) {
do {
ParseExpression();
if (count++ >= 0xFF)
ErrorAtCurrent("How many arguments do you need?");
} while (Match(T.Comma));
}
Consume(T.GroupClose, "Expected ')' after argument list");
return count;
}
/// <summary>
/// Primary Emit.
/// </summary>
internal void Emit(params byte[] data)
{
#if LOG
if (data.Length == 1)
_logger.Verbose($"Emitting {new Instruction(data[0])}");
else if (data.Length > 1)
_logger.Verbose($"Emitting {string.Join(' ', data)}");
#endif
foreach (var value in data)
Function.Segment.Instructions.Add(value);
}
/// <summary>
/// Sets instruction bytes at <paramref name="position"/>
/// </summary>
internal void Patch(byte[] bytes, long position)
{
for (int i = 0; i < bytes.Length; i++) {
Function.Segment.Instructions[position + i] = bytes[i];
}
}
internal void EmitConstant(Value constant)
{
Emit(Op.Constant);
EmitDynamic(MakeConstant(constant));
}
internal long EmitJump(Instruction instruction)
{
Emit(instruction);
Emit(-1L);
return CurrentInstruction - sizeof(long);
}
internal void PatchJump(long offset)
{
long target = CurrentInstruction - offset - sizeof(long);
Patch(target.GetBytes(), offset);
}
internal void EmitLoop(long start)
{
Emit(Op.Loop);
long offset = Function.Segment.Instructions.Count - start + sizeof(long);
Emit(offset);
}
/// <summary>
/// Emits a dynamic range of bytes into the instruction set.
/// </summary>
internal void EmitDynamic(byte[] data)
{
if (data.Length > 1)
Emit((byte)(data.Length | 0x80)); // 0x80 flag for length marker, anything else is direct value
Emit(data);
}
/// <summary>
/// Emits the given <paramref name="instruction"/>, and then adds <paramref name="data"/> using <see cref="EmitDynamic(byte[])"/>.
/// </summary>
internal void EmitDynamic(Instruction instruction, long data)
{
Emit(instruction);
EmitDynamic(data);
}
internal void EmitDynamic(long value)
=> EmitDynamic(value.GetDynamicBytes());
internal void Emit(Instruction instruction)
{
Emit(instruction.Code);
}
internal void Emit(Instruction instruction, byte[] data)
{
Emit(instruction);
Emit(data);
}
internal void Emit(IEnumerable<Instruction> instructions)
{
foreach (var i in instructions)
Emit(i);
}
internal void EmitReturn()
{
if (Builder.Type == FunctionType.Constructor) {
Emit(Op.GetLocal, 0);
} else {
Emit(Op.Null);
}
Emit(Op.Return);
}
internal void Emit(params Instruction[] instructions)
=> Emit(instructions.AsEnumerable());
internal void Emit(short value)
=> Emit(BitConverter.GetBytes(value));
internal void Emit(int value)
=> Emit(BitConverter.GetBytes(value));
internal void Emit(long value)
=> Emit(BitConverter.GetBytes(value));
internal void Emit(ulong value)
=> Emit(BitConverter.GetBytes(value));
internal void Emit(double value)
=> Emit(BitConverter.GetBytes(value));
public Token Next()
{
Previous = Current;
return Current = _reader.NextToken();
}
internal Token Consume(T expected, string errorMessage, bool acceptEof = true)
{
if ((acceptEof && Current.Type == T.Eof) || Current.Type == expected) {
Next();
return Previous;
}
ErrorAtCurrent(errorMessage);
return Token.Error(errorMessage);
}
internal bool Match(T type, bool exact = true)
{
if (!Check(type, exact))
return false;
Next();
return true;
}
internal bool Check(T type, bool exact = true)
=> exact ? Current.Type == type : (Current.Type & type) > 0;
internal void ErrorAt(Token token, string errorMessage)
=> ReportError(token, errorMessage);
internal void ErrorAtCurrent(string errorMessage)
=> ReportError(Current, errorMessage);
internal void ErrorAtPrevious(string errorMessage)
=> ReportError(Previous, errorMessage);
private void ReportError(Token token, string errorMessage)
{
#if LOG
_logger.Method();
#endif
ErrorPosition = token.Position;
#if LOG
_logger.Error($"At token index {token.Position}: {errorMessage}");
#endif
if (ThrowOnError)
throw new TokenException($"At token index {token.Position}: {errorMessage}", _reader, token);
Error?.Invoke(token, errorMessage);
}
private string ThrowIfEmpty(string? value, string? message = $"Unexpected empty string detected")
{
if (string.IsNullOrEmpty(value))
throw new TokenException("Empty string value for identifier detected", _reader, Current);
return value;
}
}

View File

@ -1,12 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Qrakhen.Qamp.Core.Compilation.Digesters;
internal interface IDigester
{
void Digest(CompilerState state);
}

View File

@ -1,379 +0,0 @@
using System.Globalization;
using Qrakhen.Qamp.Core.Execution;
using Qrakhen.Qamp.Core.Tokenization;
using Qrakhen.Qamp.Core.Values;
namespace Qrakhen.Qamp.Core.Compilation;
public static class ExpressionParser
{
private static readonly Dictionary<TokenType, Rule> _rules = new();
public static Rule Get(TokenType type) => _rules[type];
public static void And(Digester digester, bool canAssign)
{
long endJump = digester.EmitJump(OpCode.JumpIfFalse);
digester.Emit(OpCode.Pop);
digester.WeightedDigest(Weight.And);
digester.PatchJump(endJump);
}
static void Binary(Digester digester, bool canAssign)
{
TokenType operatorType = digester.Previous.Type;
Rule rule = Get(operatorType);
digester.WeightedDigest(rule.Weight + 1);
switch (operatorType) {
case TokenType.BangEqual:
digester.Emit(OpCode.Equal, OpCode.Not);
break;
case TokenType.EqualEqual:
digester.Emit(OpCode.Equal);
break;
case TokenType.Greater:
digester.Emit(OpCode.Greater);
break;
case TokenType.GreaterEqual:
digester.Emit(OpCode.Less, OpCode.Not);
break;
case TokenType.Less:
digester.Emit(OpCode.Less);
break;
case TokenType.LessEqual:
digester.Emit(OpCode.Greater, OpCode.Not);
break;
case TokenType.Plus:
digester.Emit(OpCode.Add);
break;
case TokenType.Minus:
digester.Emit(OpCode.Subtract);
break;
case TokenType.Star:
digester.Emit(OpCode.Multiply);
break;
case TokenType.Pow:
digester.Emit(OpCode.Pow);
break;
case TokenType.Slash:
digester.Emit(OpCode.Divide);
break;
case TokenType.BitwiseAnd:
digester.Emit(OpCode.BitwiseAnd);
break;
case TokenType.BitwiseOr:
digester.Emit(OpCode.BitwiseOr);
break;
case TokenType.BitwiseXor:
digester.Emit(OpCode.BitwiseXor);
break;
case TokenType.BitwiseNot:
digester.Emit(OpCode.BitwiseNot);
break;
case TokenType.BitwiseLeft:
digester.Emit(OpCode.BitwiseLeft);
break;
case TokenType.BitwiseRight:
digester.Emit(OpCode.BitwiseRight);
break;
default:
throw new QampException($"Unexpected token in binary expression: {operatorType}");
return;
}
}
static void Call(Digester digester, bool canAssign)
{
byte count = digester.Arguments();
digester.Emit(OpCode.Invoke, count);
}
static void Dot(Digester digester, bool canAssign)
{
digester.Consume(TokenType.Identifier, "Expected property name after '.'.");
long name = digester.IdentifierConstant(digester.Previous.Value);
if (canAssign && digester.Match(TokenType.Equal)) {
digester.ParseExpression();
digester.EmitDynamic(OpCode.SetMember, name);
} else if (digester.Match(TokenType.GroupOpen)) {
byte argCount = digester.Arguments();
digester.EmitDynamic(OpCode.InvokeMember, name);
digester.Emit(argCount);
} else {
digester.EmitDynamic(OpCode.GetMember, name);
}
}
static void Literal(Digester digester, bool canAssign)
{
switch (digester.Previous.Type) {
case TokenType.False:
digester.Emit(OpCode.False);
break;
case TokenType.Null:
digester.Emit(OpCode.Null);
break;
case TokenType.True:
digester.Emit(OpCode.True);
break;
default:
break;
}
}
static void Group(Digester digester, bool canAssign)
{
digester.ParseExpression();
digester.Consume(TokenType.GroupClose, "Expected ')' after expression.");
}
static void Array(Digester digester, bool canAssign)
{
int length = 0;
while (!digester.Check(TokenType.ArrayClose)) {
digester.ParseExpression();
length++;
if (digester.Check(TokenType.Comma))
digester.Next();
else
break;
}
digester.Consume(TokenType.ArrayClose, "Expected ']' after array declaration");
digester.EmitDynamic(OpCode.Array, length); // digester.MakeConstant(new Value((long)length)));
}
static void Accessor(Digester digester, bool canAssign)
{
digester.ParseExpression();
digester.Consume(TokenType.ArrayClose, "expected ] after array accessor");
if (canAssign && digester.Match(TokenType.PlusEqual)) {
digester.ParseExpression();
digester.Emit(OpCode.AddItem);
} else if (canAssign && digester.Match(TokenType.Equal)) {
digester.ParseExpression();
digester.Emit(OpCode.SetItem);
} else {
digester.Emit(OpCode.GetItem);
}
}
static void Number(Digester digester, bool canAssign)
{
Token token = digester.Previous;
string number = token.Value ?? "";
TokenType type = token.Type;
Value value = default;
if (type == TokenType.Hexadecimal)
value = new Value(long.Parse(number, NumberStyles.HexNumber));
else if (type == TokenType.Decimal)
value = new Value(double.Parse(number, NumberStyles.Float, CultureInfo.InvariantCulture));
else if (type == TokenType.Integer)
value = new Value(long.Parse(number, NumberStyles.Integer));
else
digester.ErrorAt(token, $"Could not parse number {number}");
digester.EmitConstant(value);
}
static void Or(Digester digester, bool canAssign)
{
long elseJump = digester.EmitJump(OpCode.JumpIfFalse);
long endJump = digester.EmitJump(OpCode.Jump);
digester.PatchJump(elseJump);
digester.Emit(OpCode.Pop);
digester.WeightedDigest(Weight.Or);
digester.PatchJump(endJump);
}
static void String(Digester digester, bool canAssign)
{
digester.EmitConstant(Values.Objects.String.Make(digester.Previous.Value));
}
public static void Variable(Digester digester, string name, bool canAssign)
{
OpCode Get, Set;
long variable = digester.ResolveLocal(digester.Builder, name);
if (variable > -1) {
Get = OpCode.GetLocal;
Set = OpCode.SetLocal;
} else if ((variable = digester.ResolveOuter(digester.Builder, name)) > -1) {
Get = OpCode.GetOuter;
Set = OpCode.SetOuter;
} else {
variable = digester.IdentifierConstant(name);
Get = OpCode.GetGlobal;
Set = OpCode.SetGlobal;
}
if (canAssign && digester.Match(TokenType.Assignment, false)) {
var token = digester.Previous.Type;
if (token != TokenType.Equal) {
// Get the variable's value for the operation
digester.Emit(Get);
digester.EmitDynamic(variable);
}
if (token == TokenType.Increment || token == TokenType.Decrement) {
// In the case of atomic change, we just forge the value ourselves.
digester.EmitConstant(new Value(1L));
} else {
// ... otherwise, we expect an expression on the right-hand side - parse that.
digester.ParseExpression();
}
if (token != TokenType.Equal)
{
// Append the operator
OpCode op = token switch
{
TokenType.PlusEqual or TokenType.Increment => OpCode.Add,
TokenType.MinusEqual or TokenType.Decrement => OpCode.Subtract,
TokenType.SlashEqual => OpCode.Divide,
TokenType.StarEqual => OpCode.Multiply,
TokenType.ModuloEqual => OpCode.Modulo,
_ => throw new QampException($"Unexpected token at variable assignment: {token}")
};
digester.Emit(op);
}
// Ultimately set the new value with whatever's on stack now.
digester.Emit(Set);
digester.EmitDynamic(variable);
} else {
digester.Emit(Get);
digester.EmitDynamic(variable);
}
}
public static void Variable(Digester digester, bool canAssign)
{
Variable(digester, digester.Previous.Value!, canAssign);
}
static void Base(Digester digester, bool canAssign)
{
if (digester.ClassBuilder == null)
digester.ErrorAtPrevious("We're currently not in a class. 'base' only works inside of a class context.");
else if (digester.ClassBuilder.Outer == null)
digester.ErrorAtPrevious($"{digester.ClassBuilder.Name} is not inherited from any class, so the 'base' keyword points to nothing.");
else {
digester.Consume(TokenType.Dot, "Expected '.' after 'base' keyword.");
digester.Consume(TokenType.Identifier, "Expected an identifier after 'base' keyword.");
long name = digester.IdentifierConstant(digester.Previous.Value);
Variable(digester, "this", false);
if (digester.Match(TokenType.GroupOpen)) {
byte arguments = digester.Arguments();
Variable(digester, "base", false);
digester.Emit(OpCode.InvokeBase);
digester.Emit(arguments);
} else {
Variable(digester, "base", false);
digester.Emit(OpCode.Base);
}
}
}
static void This(Digester digester, bool canAssign)
{
if (digester.ClassBuilder == null)
digester.ErrorAtPrevious("We're currently not in a class. 'this' only refers to the current instance of one.");
else
Variable(digester, false);
}
static void Modifier(Digester digester, bool canAssign)
{
Token token = digester.Previous;
digester.WeightedDigest(Weight.Modifier);
switch (token.Type) {
case TokenType.Bang:
digester.Emit(OpCode.Not);
break;
case TokenType.Minus:
digester.Emit(OpCode.Negate);
break;
case TokenType.BitwiseNot:
digester.Emit(OpCode.BitwiseNot);
break;
case TokenType.AddressOf:
digester.Emit(OpCode.Addr);
break;
}
}
static void TypeOf(Digester digester, bool canAssign)
{
digester.TypeOf();
}
static ExpressionParser()
{
_rules[TokenType.GroupOpen] = new Rule(Group, Call, Weight.Call);
_rules[TokenType.GroupClose] = new Rule(null, null, Weight.None);
_rules[TokenType.ContextOpen] = new Rule(null, null, Weight.None);
_rules[TokenType.ContextClose] = new Rule(null, null, Weight.None);
_rules[TokenType.ArrayOpen] = new Rule(Array, Accessor, Weight.Call);
_rules[TokenType.ArrayClose] = new Rule(null, null, Weight.None);
_rules[TokenType.PlusEqual] = new Rule(null, null, Weight.None);
_rules[TokenType.MinusEqual] = new Rule(null, null, Weight.None);
_rules[TokenType.Colon] = new Rule(null, Dot, Weight.Call);
_rules[TokenType.Comma] = new Rule(null, null, Weight.None);
_rules[TokenType.Dot] = new Rule(null, Dot, Weight.Call);
_rules[TokenType.Minus] = new Rule(Modifier, Binary, Weight.Term);
_rules[TokenType.Plus] = new Rule(null, Binary, Weight.Term);
_rules[TokenType.Increment] = new Rule(Modifier, Binary, Weight.Call);
_rules[TokenType.Decrement] = new Rule(Modifier, Binary, Weight.Call);
_rules[TokenType.BitwiseAnd] = new Rule(Modifier, Binary, Weight.Term);
_rules[TokenType.BitwiseOr] = new Rule(null, Binary, Weight.Term);
_rules[TokenType.BitwiseXor] = new Rule(null, Binary, Weight.Term);
_rules[TokenType.BitwiseLeft] = new Rule(null, Binary, Weight.Term);
_rules[TokenType.BitwiseRight] = new Rule(null, Binary, Weight.Term);
_rules[TokenType.BitwiseNot] = new Rule(Modifier, null, Weight.Term);
_rules[TokenType.Semicolon] = new Rule(null, null, Weight.None);
_rules[TokenType.Slash] = new Rule(null, Binary, Weight.Factor);
_rules[TokenType.Star] = new Rule(null, Binary, Weight.Factor);
_rules[TokenType.Pow] = new Rule(null, Binary, Weight.Factor);
_rules[TokenType.Bang] = new Rule(Modifier, null, Weight.None);
_rules[TokenType.BangEqual] = new Rule(null, Binary, Weight.Equal);
_rules[TokenType.Equal] = new Rule(null, null, Weight.None);
_rules[TokenType.EqualEqual] = new Rule(null, Binary, Weight.Equal);
_rules[TokenType.Greater] = new Rule(null, Binary, Weight.Compare);
_rules[TokenType.GreaterEqual] = new Rule(null, Binary, Weight.Compare);
_rules[TokenType.Less] = new Rule(null, Binary, Weight.Compare);
_rules[TokenType.LessEqual] = new Rule(null, Binary, Weight.Compare);
_rules[TokenType.Identifier] = new Rule(Variable, null, Weight.None);
_rules[TokenType.String] = new Rule(String, null, Weight.None);
_rules[TokenType.Integer] = new Rule(Number, null, Weight.None);
_rules[TokenType.Hexadecimal] = new Rule(Number, null, Weight.None);
_rules[TokenType.Decimal] = new Rule(Number, null, Weight.None);
_rules[TokenType.And] = new Rule(null, And, Weight.And);
_rules[TokenType.Class] = new Rule(null, null, Weight.None);
_rules[TokenType.Else] = new Rule(null, null, Weight.None);
_rules[TokenType.False] = new Rule(Literal, null, Weight.None);
_rules[TokenType.For] = new Rule(null, null, Weight.None);
_rules[TokenType.Function] = new Rule(null, null, Weight.None);
_rules[TokenType.If] = new Rule(null, null, Weight.None);
_rules[TokenType.Null] = new Rule(Literal, null, Weight.None);
_rules[TokenType.Or] = new Rule(null, Or, Weight.Or);
_rules[TokenType.Print] = new Rule(null, null, Weight.None);
_rules[TokenType.TypeOf] = new Rule(TypeOf, null, Weight.None);
_rules[TokenType.AddressOf] = new Rule(Modifier, null, Weight.Term);
_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.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);
_rules[TokenType.While] = new Rule(null, null, Weight.None);
_rules[TokenType.Error] = new Rule(null, null, Weight.None);
_rules[TokenType.Eof] = new Rule(null, null, Weight.None);
}
}

View File

@ -1,10 +0,0 @@
namespace Qrakhen.Qamp.Core.Compilation;
public delegate void RuleDelegate(Digester digester, bool canAssign);
public readonly struct Rule(RuleDelegate? prefix = null, RuleDelegate? infix = null, Weight weight = Weight.None)
{
public readonly RuleDelegate? Prefix = prefix;
public readonly RuleDelegate? Infix = infix;
public readonly Weight Weight = weight;
}

View File

@ -1,16 +0,0 @@
namespace Qrakhen.Qamp.Core.Compilation;
public enum Weight
{
None = 0,
Assign = 1,
Or = 2,
And = 3,
Equal = 4,
Compare = 5,
Term = 6,
Factor = 7,
Modifier = 8,
Call = 9,
Primary = 10
}

View File

@ -1,57 +0,0 @@
using static System.Runtime.InteropServices.JavaScript.JSType;
namespace Qrakhen.Qamp.Core.IO;
public static class Console
{
public static OutputStreamMessageHandler StdOut;
public static ErrorStreamMessageHandler StdErr;
public static InputStreamListenHandler StdIn;
public static MessageFormatter Formatter;
static Console()
{
StdOut = System.Console.Out.WriteLine;
StdErr = System.Console.Error.WriteLine;
StdIn = System.Console.In.ReadLine;
Formatter = (p) => {
if (p.Length == 0)
return [];
return p.Select(x => x.ToString()).ToArray() ?? [];
};
}
public static void Write(params object[] parameters)
{
if (StdOut == null)
return;
var lines = Formatter(parameters);
foreach (var line in lines)
StdOut(line);
}
public static string? Read()
{
if (StdIn == null)
throw new InvalidOperationException($"No stdin provided, can not read input stream.");
return StdIn();
}
public static void Error(params object[] parameters)
{
if (StdErr == null)
Write(parameters);
var lines = Formatter(parameters);
foreach (var line in lines)
StdErr(line);
}
}
public delegate string? InputStreamListenHandler();
public delegate void OutputStreamMessageHandler(string? message);
public delegate void ErrorStreamMessageHandler(string? message);
public delegate string?[] MessageFormatter(params object[] parameters);

View File

@ -1,35 +0,0 @@
using Qrakhen.Qamp.Core.Collections.Abstractions;
using Qrakhen.Qamp.Core.Tokenization;
using Qrakhen.Qamp.Core.Values;
namespace Qrakhen.Qamp.Core;
public class QampException(string message, object? context = null)
: Exception(message)
{
public readonly object? Context = context;
}
public class ReaderException(string message, IReader<Token> reader)
: QampException(message, reader.CurrentPosition)
{
public readonly IReader<Token> Reader = reader;
}
public class TokenException(string message, IReader<Token> reader, Token token)
: ReaderException($"TokenError {token} at {token.Position}: {message}", reader)
{
public readonly Token Token = token;
}
public class RuntimeException(string message, object? context = null)
: QampException(message, context);
public class DivisionByZeroException(string? message, object? context = null)
: RuntimeException(message ?? "Can not divide by zero", context);
public class ConversionException(string message, Value value, object? context = null)
: RuntimeException(message, context)
{
public readonly Value Value = value;
}

View File

@ -1,288 +0,0 @@
using Qrakhen.Qamp.Core.Collections;
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 class ArithmeticResolver : IOperationResolver
{
private readonly ILogger _logger = LoggerService.Get(nameof(ArithmeticResolver));
private readonly Register<OpCode, OperationHandler> _operations;
public bool CanResolve(OpCode opCode) => opCode.IsHandledByAlu();
private Operation PopOperation(Runner runner, OpCode code)
{
#if LOG
_logger.Method(code);
#endif
if (code.IsUnary())
return new Operation(code, runner.Pop(), Value.Void);
Value right = runner.Pop();
Value left = runner.Pop();
return new Operation(code, left, right);
}
public OperationResult Resolve(Runner runner, OpCode opCode)
{
Operation operation = PopOperation(runner, opCode);
#if LOG
_logger.Method($"{operation.Left} {operation.OpCode} {operation.Right}");
#endif
// p sure switch is faster here (as opposed to dict lookup)
Value result = operation.OpCode switch {
OpCode.Equal => Equal(operation),
OpCode.Greater => Greater(operation),
OpCode.Less => Less(operation),
OpCode.Not => Not(operation),
OpCode.Add => Add(operation),
OpCode.Subtract => Subtract(operation),
OpCode.Divide => Divide(operation),
OpCode.Modulo => Modulo(operation),
OpCode.Multiply => Multiply(operation),
OpCode.Pow => Pow(operation),
OpCode.Negate => Negate(operation),
OpCode.BitwiseOr => BitwiseOr(operation),
OpCode.BitwiseAnd => BitwiseAnd(operation),
OpCode.BitwiseLeft => BitwiseLeft(operation),
OpCode.BitwiseRight => BitwiseRight(operation),
OpCode.BitwiseNot => BitwiseInvert(operation),
OpCode.BitwiseXor => BitwiseXor(operation),
_ => throw new NotImplementedException($"Unknown operator {operation.OpCode}.")
};
#if LOG
_logger.Verbose($"Result: {result}");
#endif
runner.Push(result);
return OperationResult.MakeSuccess(result);
}
public ArithmeticResolver()
{
_operations = new Register<OpCode, OperationHandler> {
{ OpCode.Equal, Equal },
{ OpCode.Greater, Greater },
{ OpCode.Less, Less },
{ OpCode.Not, Not },
{ OpCode.Add, Add },
{ OpCode.Subtract, Subtract },
{ OpCode.Divide, Divide },
{ OpCode.Modulo, Modulo },
{ OpCode.Multiply, Multiply },
{ OpCode.Pow, Pow },
{ OpCode.Negate, Negate },
{ OpCode.BitwiseOr, BitwiseOr },
{ OpCode.BitwiseAnd, BitwiseAnd },
{ OpCode.BitwiseLeft, BitwiseLeft },
{ OpCode.BitwiseRight, BitwiseRight },
{ OpCode.BitwiseNot, BitwiseInvert },
{ OpCode.BitwiseXor, BitwiseXor }
};
}
public static Value Equal(Operation operation)
{
Value a = operation.Left;
Value b = operation.Right;
#if SAFE_OPERATIONS
Assert.NotVoid(a, b);
Assert.IsPrimitive(a, b);
#endif
return new Value(a.Dynamic == b.Dynamic);
}
public static Value Greater(Operation operation)
{
Value a = operation.Left;
Value b = operation.Right;
#if SAFE_OPERATIONS
Assert.NotVoid(a, b);
Assert.IsPrimitive(a, b);
#endif
return new Value(a.Dynamic > b.Dynamic);
}
public static Value Less(Operation operation)
{
Value a = operation.Left;
Value b = operation.Right;
#if SAFE_OPERATIONS
Assert.NotVoid(a, b);
Assert.IsPrimitive(a, b);
#endif
return new Value(a.Dynamic < b.Dynamic);
}
public static Value Not(Operation operation)
{
Value value = operation.Left;
#if SAFE_OPERATIONS
Assert.NotVoid(value);
#endif
ulong inner = value.Unsigned;
return new Value(inner == 0);
}
public static Value Add(Operation operation)
{
Value a = operation.Left;
Value b = operation.Right;
#if SAFE_OPERATIONS
Assert.NotVoid(a, b);
#endif
if (a.IsString)
return Values.Objects.String.Make($"{a}{b}");
#if SAFE_OPERATIONS
Assert.IsPrimitive(a, b);
#endif
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;
#if SAFE_OPERATIONS
Assert.NotVoid(a, b);
Assert.IsPrimitive(a, b);
#endif
return new Value(a.Dynamic - b.Dynamic);
}
public static Value Divide(Operation operation)
{
Value a = operation.Left;
Value b = operation.Right;
#if SAFE_OPERATIONS
Assert.NotVoid(a, b);
Assert.IsPrimitive(a, b);
Assert.NotZero(b);
#endif
return new Value(a.Dynamic / b.Dynamic);
}
public static Value Modulo(Operation operation)
{
Value a = operation.Left;
Value b = operation.Right;
#if SAFE_OPERATIONS
Assert.NotVoid(a, b);
Assert.IsPrimitive(a, b);
Assert.NotZero(b);
#endif
return new Value(a.Dynamic % b.Dynamic);
}
public static Value Multiply(Operation operation)
{
Value a = operation.Left;
Value b = operation.Right;
#if SAFE_OPERATIONS
Assert.NotVoid(a, b);
Assert.IsPrimitive(a, b);
#endif
return new Value(a.Dynamic * b.Dynamic);
}
public static Value Pow(Operation operation)
{
Value a = operation.Left;
Value b = operation.Right;
#if SAFE_OPERATIONS
Assert.NotVoid(a, b);
Assert.IsPrimitive(a, b);
#endif
return new Value(Math.Pow(a.GetDecimal(), b.GetDecimal()));
}
public static Value Negate(Operation operation)
{
Value value = operation.Left;
#if SAFE_OPERATIONS
Assert.NotVoid(value);
Assert.IsPrimitive(value);
#endif
return new Value(-value.Dynamic);
}
public static Value BitwiseOr(Operation operation)
{
Value a = operation.Left;
Value b = operation.Right;
#if SAFE_OPERATIONS
Assert.NotVoid(a, b);
Assert.IsPrimitive(a, b);
Assert.IsInteger(a, b);
#endif
return new Value(a.Dynamic | b.Dynamic);
}
public static Value BitwiseAnd(Operation operation)
{
Value a = operation.Left;
Value b = operation.Right;
#if SAFE_OPERATIONS
Assert.NotVoid(a, b);
Assert.IsPrimitive(a, b);
Assert.IsInteger(a, b);
#endif
return new Value(a.Dynamic & b.Dynamic);
}
public static Value BitwiseXor(Operation operation)
{
Value a = operation.Left;
Value b = operation.Right;
#if SAFE_OPERATIONS
Assert.NotVoid(a, b);
Assert.IsPrimitive(a, b);
Assert.IsInteger(a, b);
#endif
return new Value(a.Dynamic ^ b.Dynamic);
}
public static Value BitwiseLeft(Operation operation)
{
Value a = operation.Left;
Value b = operation.Right;
#if SAFE_OPERATIONS
Assert.NotVoid(a, b);
Assert.IsPrimitive(a, b);
Assert.IsInteger(a, b);
#endif
return new Value(a.Dynamic << (int)(b.Dynamic ?? 0));
}
public static Value BitwiseRight(Operation operation)
{
Value a = operation.Left;
Value b = operation.Right;
#if SAFE_OPERATIONS
Assert.NotVoid(a, b);
Assert.IsPrimitive(a, b);
Assert.IsInteger(a, b);
#endif
return new Value(a.Dynamic >> (int)(b.Dynamic ?? 0));
}
public static Value BitwiseInvert(Operation operation)
{
Value a = operation.Left;
#if SAFE_OPERATIONS
Assert.NotVoid(a);
Assert.IsPrimitive(a);
Assert.IsInteger(a);
#endif
return new Value(~a.Dynamic);
}
}

View File

@ -1,16 +0,0 @@
namespace Qrakhen.Qamp.Core.Execution;
public readonly record struct Instruction(byte Code)
{
public OpCode OpCode => (OpCode)Code;
public static implicit operator byte(Instruction instruction) => instruction.Code;
public static implicit operator Instruction(byte code) => new(code);
public static implicit operator OpCode(Instruction instruction) => (OpCode)instruction.Code;
public static implicit operator Instruction(OpCode code) => new((byte)code);
public override string ToString()
{
return $"<{Code:X4} | {OpCode}>";
}
}

View File

@ -1,65 +0,0 @@
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[Cursor];
public InstructionPtr(Segment segment, int position = 0) : base(segment.Instructions, position)
{
Segment = segment;
}
public long NextLong()
{
long value = Segment.ReadLong(Cursor);
Cursor += 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(Cursor, out int read);
Cursor += 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.Cursor + value];
public static Instruction operator -(InstructionPtr ptr, int value)
=> ptr.Segment.Instructions[ptr.Cursor - value];
public static InstructionPtr operator ++(InstructionPtr ptr)
{
ptr.Cursor++;
return ptr;
}
public static InstructionPtr operator --(InstructionPtr ptr)
{
ptr.Cursor--;
return ptr;
}
}

View File

@ -1,36 +0,0 @@
using Qrakhen.Qamp.Core.Collections.Abstractions;
namespace Qrakhen.Qamp.Core.Execution;
public class Instructions<T>(IEnumerable<T> instructions) :
ISteppable<T>,
IPeekable<T>,
ISeekable<long, T>
{
private readonly T[] _instructions = instructions.ToArray();
public long Position { get; private set; } = 0;
public long Length => _instructions.LongLength;
public bool Done => Position >= _instructions.Length;
public T this[long offset] => offset < Length ?
_instructions[offset] :
throw new ArgumentOutOfRangeException($"Tried to access offset beyond instruction length {offset}:{Length}");
public T Seek(long position)
{
Position = position;
return _instructions[Position];
}
public T Next()
{
return _instructions[Position++];
}
public T Peek(int delta = 0)
{
return _instructions[Position + delta];
}
}

View File

@ -1,132 +0,0 @@
namespace Qrakhen.Qamp.Core.Execution;
public class SerializedEnumAttribute(Type serializedType) : Attribute
{
public Type SerializedType { get; } = serializedType;
}
/// <summary>
///
/// </summary>
/// <remarks>
/// In order to keep instruction files small, all op codes are crammed into 8 bits,
/// and operation flags aren't flags, but rather should be seen as IDs (the first 4 bits)
/// and the operation itself is a number made from the last 4 bits.
/// & 0b11110000 => Handler
/// & 0b00001111 => Operation
/// </remarks>
[SerializedEnum(typeof(byte))]
public enum OpCode
{
/// <summary>
/// Mask bits, to be used like <c>opCode &amp; F_Mask == F_Operation</c>, for example.
/// </summary>
Mask_Handler = 0xF0,
Mask_Operation = 0x0F,
Error = -1,
Void = 0,
F_Static = 0x10,
None = F_Static | 1,
True = F_Static | 2,
False = F_Static | 3,
Constant = F_Static | 4,
Null = F_Static | 5,
Cast = F_Static | 6,
Pop = F_Static | 7,
F_Retrieve = 0x20,
Val = F_Retrieve | 1,
Ref = F_Retrieve | 2,
Addr = F_Retrieve | 3,
F_Names = 0x30,
SetGlobal = F_Names | 1,
GetGlobal = F_Names | 2,
GetLocal = F_Names | 3,
SetLocal = F_Names | 4,
GetOuter = F_Names | 5,
SetOuter = F_Names | 6,
GetMember = F_Names | 7,
SetMember = F_Names | 8,
DefineGlobal = F_Names | 9,
CloseOuter = F_Names | 10,
//AssignValue = 0x0, // unsure
//AssignReference = 0x0, // unsure
F_Arithmetic = 0x40,
Add = F_Arithmetic | 1,
Subtract = F_Arithmetic | 2,
Multiply = F_Arithmetic | 3,
Pow = F_Arithmetic | 4,
Divide = F_Arithmetic | 5,
Modulo = F_Arithmetic | 6,
Negate = F_Arithmetic | 7,
F_Bitwise = 0x50,
BitwiseAnd = F_Bitwise | 1,
BitwiseOr = F_Bitwise | 2,
BitwiseXor = F_Bitwise | 3,
BitwiseLeft = F_Bitwise | 4,
BitwiseRight = F_Bitwise | 5,
BitwiseNot = F_Bitwise | 6,
F_Boolean = 0x60,
Not = F_Boolean | 1,
Equal = F_Boolean | 2,
Greater = F_Boolean | 3,
Less = F_Boolean | 4,
F_Collection = 0x70,
Array = F_Collection | 1,
List = F_Collection | 2,
Structure = F_Collection | 3,
GetItem = F_Collection | 4,
SetItem = F_Collection | 5,
AddItem = F_Collection | 6,
RemoveItem = F_Collection | 7,
F_Control = 0x80,
Return = F_Control | 1,
Jump = F_Control | 2,
JumpIfFalse = F_Control | 3,
Loop = F_Control | 4,
Invoke = F_Control | 5,
InvokeBase = F_Control | 6,
InvokeMember = F_Control | 7,
Context = F_Control | 8,
F_Class = 0x90,
Class = F_Class | 1,
Member = F_Class | 2,
Base = F_Class | 3,
Inherit = F_Class | 4,
Method = F_Class | 5,
Static = F_Class | 6,
F_Meta = 0xa0,
Print = F_Meta | 1,
PrintStack = F_Meta | 2,
PrintGlobals = F_Meta | 3,
PrintExpr = F_Meta | 4,
Typeof = F_Meta | 5,
F_Module = 0xf0,
Export = F_Module | 1, // probably shouldnt be an op code but rather be done during digestion
Import = F_Module | 2, // probably shouldnt be an op code but rather be done during digestion
}
public static class OpCodeExtensions
{
public static bool IsUnary(this OpCode op)
=> op is OpCode.Not or OpCode.Negate or OpCode.BitwiseNot;
public static bool IsHandledByAlu(this OpCode op)
=> (op & OpCode.Mask_Handler) is OpCode.F_Arithmetic or OpCode.F_Bitwise or OpCode.F_Boolean;
public static string ToString(this OpCode op)
=> $"{op & OpCode.Mask_Handler}: {op} ({(byte)op:x2})";
}

View File

@ -1,874 +0,0 @@
using Qrakhen.Qamp.Core.Collections;
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;
using T = Values.ValueType;
public class Call
{
public Context Context;
public InstructionPtr Instruction;
public Pointer<Value> StackPtr;
public Call(Context context, StackLike<Value> stack, long stackOffset)
{
Context = context;
Instruction = new InstructionPtr(Context.Function.Segment);
StackPtr = new Pointer<Value>(stack, stackOffset);
}
}
public interface IOperationResolver
{
bool CanResolve(OpCode code);
OperationResult Resolve(Runner runner, OpCode code);
}
public struct OperationResult
{
public bool Success;
public Value Value;
public RuntimeException? Error;
public static OperationResult MakeSuccess(Value value)
{
return new OperationResult { Success = true, Value = value };
}
public static OperationResult MakeError(RuntimeException exception)
{
return new OperationResult { Success = false, Value = Value.Void, Error = exception };
}
}
public class OperationRouter
{
private readonly List<IOperationResolver> _handlers = [];
private readonly Dictionary<OpCode, IOperationResolver> _cache = [];
public void Register(IOperationResolver resolver)
{
_handlers.Add(resolver);
}
public IOperationResolver? Route(Runner runner, OpCode opCode)
{
if (!_cache.TryGetValue(opCode, out IOperationResolver? resolver)) {
resolver = _handlers.FirstOrDefault(h => h.CanResolve(opCode));
if (resolver == null)
return null;
_cache[opCode] = resolver;
}
return _cache[opCode];
}
}
public class Runner : IDisposable
{
private readonly ILogger _logger = LoggerService.Get<Runner>();
private readonly OperationRouter _router = new OperationRouter();
private Value _runnerReturned = Value.Void;
public readonly Options Options;
private List<IOperationResolver> _resolvers = [];
private StackLike<Call> _calls;
// todo: maybe have a pointer everywhere to stacks or arrays and use those rather than built-in pointers or cursors... ?
private StackLike<Value> _stack;
private long _cursor => _stack.Position;
private List<Outer> Outers = [];
private Register<string, Value> Globals = new();
private Register<string, Value> Imports = new();
private Register<string, Value> Exports = new();
public Runner(Options options)
{
Options = options;
_calls = new StackLike<Call>(options.MaxCalls);
_stack = new StackLike<Value>(options.InitialStackSize);
IO.Console.StdOut ??= options.StdOut;
IO.Console.StdIn ??= options.StdIn;
Globals["time"] = Obj.Create(new NativeFunction("time",
v => new Value(DateTime.Now.Ticks / 100)));
Globals["ftime"] = Obj.Create(new NativeFunction("ftime",
v =>
String.Make(
new DateTime(v[0].Signed * 10)
.ToString(
v[1].GetString() ??
Timestamp.DefaultFormat)),
"time",
"format"));
Globals["sin"] = Obj.Create(new NativeFunction("sin",
v => new Value(Math.Sin(v[0].GetDecimal())), "v"));
Globals["cos"] = Obj.Create(new NativeFunction("cos",
v => new Value(Math.Cos(v[0].GetDecimal())), "v"));
Globals["sqrt"] = Obj.Create(new NativeFunction("sqrt",
v => new Value(Math.Sqrt(v[0].GetDecimal())), "v"));
Globals["exit"] = Obj.Create(new NativeFunction("exit", v => {
Environment.Exit((int)(v.FirstOrDefault().Signed));
return Value.Void;
},
"code?"));
Globals["read"] = Obj.Create(new NativeFunction("read", v =>
String.Make(IO.Console.StdIn())));
Globals["write"] = Obj.Create(new NativeFunction("write", v => {
try {
if (v[0].GetString() is string content) {
IO.Console.StdOut(content);
}
} catch { }
return Value.Void;
},
"content"));
Globals["fread"] = Obj.Create(new NativeFunction("fread", v =>
(v[0].GetString() is string path && File.Exists(Path.GetFullPath(path))) ?
String.Make(
File.ReadAllText(
Path.GetFullPath(path))) :
Value.Void,
"path"));
Globals["fwrite"] = Obj.Create(new NativeFunction("fwrite", v => {
try {
if (v[0].GetString() is string path) {
File.WriteAllText(
path,
v[1].GetString());
} else {
return Value.False;
}
} catch {
return Value.False;
}
return Value.True;
},
"path",
"content?"));
_router.Register(new ArithmeticResolver());
}
public ExecutionResult Run(Stream stream, params string[] parameters)
{
_logger.Method();
if (_stack.Position != 0) {
_logger.Warn($"Something went terribly wrong, stack cursor is at {_stack.Position} before execution.\n --- Resetting Stack ---");
_stack = new StackLike<Value>(Options.InitialStackSize);
}
DateTime start = DateTime.Now;
Benchmark.Start($"Compile Start");
// cached value, used to directly return resulting values to the calling process
_runnerReturned = Value.Void;
using Reader reader = new Reader(stream);
Compilation.Digester digester = new(reader);
Function function = digester.Digest();
Benchmark.End($"Compile End");
// compilation result taste test
if (stream.Length > 64)
File.WriteAllBytes($"./func.{DateTime.Now:yyyyMMdd_HHmmss}.sqi", function.Serialize(true));
// run the code in a new context.
// global namespace bleeds into here anyway, executing directly in global namespace is a 'safety risk'.
Context closure = new Context(function);
Push(Obj.Create(closure));
Invoke(closure, 0);
Benchmark.Start($"Execution Start");
RunnerResult result = Execute();
Benchmark.End($"Execution End");
DateTime end = DateTime.Now;
return new ExecutionResult(result,
_runnerReturned,
start,
end,
parameters);
}
private bool TestHandler(Op code, Op handler) => (code & Op.Mask_Handler) == handler;
private RunnerResult Execute()
{
#if LOG
_logger.Method();
#endif
Call call = _calls.Peek(-1);
do {
Op opCode = call.Instruction.Next();
#if LOG
_logger.Verbose($"OpCode: {opCode}");
#endif
//IO.Console.Write($"{opCode}: \n - {string.Join("\n - ", Stack.ToArray().Subset(0, Stack.Count))}");
IOperationResolver? resolver = _router.Route(this, opCode);
if (resolver != null)
{
OperationResult result = resolver.Resolve(this, opCode);
if (!result.Success)
{
_logger.Error(result.Error?.Message);
}
continue;
}
switch (opCode) {
case Op.Null:
Push(Value.Void);
break;
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.Addr: {
Value value = Pop();
if (!value.IsObj)
return Error($"can not get address of a value on stack");
Push(new Value(value.Ptr.Address));
break;
}
case Op.GetGlobal: {
string? name = call.Instruction.GetStringConstant(call.Instruction.NextDynamic())?.Value;
if (string.IsNullOrEmpty(name))
return Error($"tried to get global variable with empty name");
if (!Globals.Has(name))
Push(Value.Void);
else
Push(Globals[name]);
#if LOG
_logger.Verbose($"got global {name} ({Peek()})");
#endif
break;
}
case Op.SetGlobal: {
string? name = call.Instruction.GetStringConstant(call.Instruction.NextDynamic())?.Value;
if (string.IsNullOrEmpty(name))
return Error($"tried to set global variable with empty name");
if (!Globals.Has(name))
return Error($"tried to set a value of non-existing global variable");
Globals[name] = Peek();
#if LOG
_logger.Verbose($"set global {name} = {Globals[name]}");
#endif
break;
}
case Op.DefineGlobal: {
string? name = call.Instruction.GetStringConstant(call.Instruction.NextDynamic())?.Value;
if (string.IsNullOrEmpty(name))
return Error($"tried to define global variable with empty name");
Globals[name] = Pop();
#if LOG
_logger.Verbose($"defined global {name} as {Globals[name]}");
#endif
break;
}
case Op.GetLocal: {
long slot = call.Instruction.NextDynamic();
#if LOG
_logger.Verbose($"getting slot {slot} which is {call.StackPtr.Get(slot)}");
#endif
Push(call.StackPtr.Get(slot));
break;
}
case Op.SetLocal: {
long slot = call.Instruction.NextDynamic();
#if LOG
_logger.Verbose($"setting stackptr {slot} to {Peek()}");
#endif
call.StackPtr.Set(slot, Peek());
break;
}
case Op.GetOuter: {
long slot = call.Instruction.NextDynamic();
Push(call.Context.Outers[slot].Value);
break;
}
case Op.SetOuter: {
long slot = call.Instruction.NextDynamic();
call.Context.Outers[slot].Value = Peek();
break;
}
case Op.GetMember: {
Value value = Peek();
if (!value.Is(T.Instance))
return Error("Only instances have members.");
Instance instance = value.Ptr.As<Instance>()!;
string name = call.Instruction.NextString().Value!;
if (instance.Values.TryGet(name, out Value member)) {
Pop(); // remove instance from stack
Push(member);
} else if (!BindMethod(instance.Class, name)) {
return Error($"Could not bind member {name} to instance {instance} of class {instance.Class.Name}!");
}
break;
}
case Op.SetMember: {
Value value = Peek(-2);
if (!value.Is(T.Instance))
return Error("Only instances have members.");
Instance instance = value.Ptr.As<Instance>()!;
string name = call.Instruction.NextString().Value!;
instance.Values.Set(name, Peek());
Value move = Pop();
Pop(); // remove instance from stack
Push(move);
break;
}
case Op.Base: {
string name = call.Instruction.NextString().Value!;
Class @class = Pop().Ptr.As<Class>()!;
if (!BindMethod(@class, name))
return Error($"Could not bind member {name} to base class {@class.Name}");
break;
}
case Op.Array: {
int length = (int)call.Instruction.NextDynamic();
Value[] values = new Value[length];
for (int i = length - 1; i >= 0; i--) {
values[i] = Pop();
}
var array = new Values.Objects.Array(values);
Value value = Obj.Create(array);
Push(value);
break;
}
case Op.GetItem: {
Value index = Pop();
Value items = Pop();
if (!items.Is(T.ItemProvider, false))
return Error($"Can not access {index} of {items} - it is not an ItemProvider! (Array, List, etc.)");
if (items.Is(T.Array))
Push(items.Ptr.As<Values.Objects.Array>()!.Get(index));
else if (items.Is(T.List))
Push(items.Ptr.As<List>()!.Get(index));
else if (items.Is(T.Structure))
Push(items.Ptr.As<Structure>()!.Get(index));
else if (items.Is(T.String))
Push(items.Ptr.As<String>()!.SubString(index, new Value(1L)));
else
return Error($"Unsupported native accessor for type {items.Type}!");
break;
}
case Op.SetItem: {
Value value = Pop();
Value index = Pop();
Value items = Pop();
if (!items.Is(T.ItemProvider, false))
return Error($"Can not access {index} of {items} - it is not an ItemProvider! (Array, List, etc.)");
if (items.Is(T.Array))
items.Ptr.As<Values.Objects.Array>()!.Set(index, value);
else if (items.Is(T.List))
items.Ptr.As<List>()!.Set(index, value);
else if (items.Is(T.Structure))
items.Ptr.As<Structure>()!.Set(index, value);
else
return Error($"Unsupported native accessor for type {items.Type}!");
break;
}
case Op.AddItem: {
Value value = Pop();
Value items = Pop();
if (!items.Is(T.List))
return Error($"Can not add {value} to {items} - only lists :[] support the native add <+ operator.");
items.Ptr.As<List>()!.Add(value);
break;
}
case Op.Jump: {
long delta = call.Instruction.NextLong();
call.Instruction.Cursor += delta;
break;
}
case Op.JumpIfFalse: {
long delta = call.Instruction.NextLong();
if (Peek().IsFalsy)
call.Instruction.Cursor += delta;
break;
}
case Op.Loop: {
long delta = call.Instruction.NextLong();
call.Instruction.Cursor -= 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: {
Value result = Pop();
CloseOuters(call.StackPtr.Branch());
_calls.Pop();
if (_calls.Count == 0) {
if (_stack.Count > 0) {
// todo: very bad fix for our stack problem
Pop();
} else {
_logger.Warn($"Critical issue at end of execution detected - stack was empty at return.");
}
return RunnerResult.OK;
}
_stack.Decimate(call.StackPtr.Cursor);
Push(result);
call = _calls.Peek();
break;
}
case Op.Class: {
Push(Obj.Create(new Class(call.Instruction.NextString().Value!)));
break;
}
case Op.Inherit: {
Value @class = Peek(-2);
if (!@class.Is(T.Class))
return Error($"Can not inherit from {@class}, value must be of type Class.");
Class baseClass = @class.Ptr.As<Class>()!;
Class inheritingClass = Peek().Ptr.As<Class>()!;
foreach (var member in baseClass.Members) {
if (!inheritingClass.Members.Has(member.Key))
inheritingClass.Members.Add(member.Key, member.Value);
}
Pop();
break;
}
case Op.Method:
DefineMember(call.Instruction.NextString().Value!);
break;
case Op.Typeof: {
Value value = Pop();
string type;
if (value.Is(T.Class))
type = value.Ptr.As<Class>()!.Name;
else if (value.Is(T.Instance))
type = $"instance of {value.Ptr.As<Instance>()!.Class.Name}";
else if (value.Is(T.Function))
type = $"function {value.Ptr.As<Function>()!.Name}";
else if (value.Is(T.Method))
type = $"method {value.Ptr.As<Method>()!.Function.Name}";
else
type = value.Type.ToString();
Push(String.Make(type));
break;
}
case Op.Print: {
Value value = Pop();
IO.Console.Write(value);
break;
}
case Op.PrintStack: {
for (int i = 0; i < _cursor; i++) {
IO.Console.Write($"{_stack[i].ToString(true)}");
}
break;
}
case Op.PrintGlobals: {
foreach (var value in Globals) {
IO.Console.Write($"{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.Cursor);
return RunnerResult.OK;
}
private Outer CaptureOuter(Pointer<Value> target)
{
Outer? outer = null;
for (int i = Outers.Count; i-- > 0;) {
outer = Outers[i];
if (outer.Target.Cursor <= target.Cursor)
break;
}
if (outer != null && outer.Target.Cursor == target.Cursor)
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.Cursor < last.Cursor)
return;
outer.Close();
}
}
private bool InvokeExtension(Value target, ExtensionMethod extension, int argumentCount)
{
#if LOG
_logger.Method(extension);
#endif
if (argumentCount != extension.Parameters.Length) {
Error($"{extension} expects {extension.Parameters.Length} arguments, received {argumentCount}");
return false;
}
var values = new Value[argumentCount];
for (int i = 0; i < argumentCount; i++) {
values[i] = Peek(-i - 1);
}
_stack.Decimate(_stack.Position - argumentCount - 1);
Value result = extension.Call(target, values);
Push(result);
return true;
}
private bool InvokeNative(NativeFunction native, int argumentCount)
{
#if LOG
_logger.Method(native);
#endif
if (argumentCount != native.Arguments.Length) {
Error($"{native} expects {native.Arguments.Length} arguments, received {argumentCount}");
return false;
}
var values = new Value[argumentCount];
for (int i = 0; i < argumentCount; i++) {
values[i] = Peek(-i - 1);
}
_stack.Decimate(_stack.Position - argumentCount - 1);
Value result = native.Target.Invoke(values);
Push(result);
return true;
}
private bool Invoke(Context closure, int argumentCount)
{
#if LOG
_logger.Method();
#endif
if (argumentCount != closure.Function.ArgumentCount) {
Error($"Expected {closure.Function.ArgumentCount} arguments but got {argumentCount}");
return false;
}
if (_calls.Count > Options.MaxCalls) {
Error($"Stack overflow {_calls.Count}");
return false;
}
Call call = new(closure, _stack, _cursor - argumentCount - 1);
_calls.Push(call);
return true;
}
private bool Invoke(string methodName, int argumentCount)
{
Value value = Peek(-argumentCount - 1);
TypeInfo typeInfo = value.TypeInfo;
if (!value.Is(T.Instance)) {
ExtensionMethod? extension = ExtensionMethod.Get(typeInfo, methodName, argumentCount);
if (extension == null) {
Error($"Could not invoke {methodName} on {value}, no method or extension by that name found for {typeInfo}.");
return false;
}
return InvokeExtension(value, extension, argumentCount);
}
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 invoke non-primitive values! Tried to invoke {value.ToString(true)}", value);
return false;
}
if (value.Is(T.Native)) {
if (value.Ptr.As<NativeFunction>() is NativeFunction native)
return InvokeNative(native, argumentCount);
return false; // unlikely edge case, natives are always set directly via the global registry
} else 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.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<Context>()!.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();
}
public Value Peek(int delta = -1)
{
#if LOG
_logger.Method();
#endif
return _stack.Peek(delta);
}
public Value Pop()
{
#if LOG
_logger.Method();
#endif
return _stack.Pop();
}
public void Push(Value value)
{
#if LOG
_logger.Method($"{value}");
#endif
_stack.Push(value);
}
private RunnerResult Error(string message, object? context = null, bool @throw = true)
{
#if LOG
_logger.Method(message);
#endif
#if LOG
_logger.Error(message, context);
#endif
Panic();
if (@throw)
throw new QampException(message, context);
return RunnerResult.Execution;
}
private void Panic()
{
#if LOG
_logger.Method();
#endif
_stack.Decimate(0);
}
public void Dispose()
{
#if LOG
_logger.Method();
#endif
}
}
public class GarbageCollector
{
}
public enum RunnerResult
{
OK = 0x0000,
Error = 0x1000,
Compilation = 0x0001,
Execution = 0x0002,
Unknown = -1
}
public readonly struct ExecutionResult(
RunnerResult code = RunnerResult.Unknown,
Value? returned = null,
DateTime? started = null,
DateTime? finished = null,
string[] parameters = null)
{
public RunnerResult RunnerResult { get; } = code;
public Value? Returned { get; } = returned;
public DateTime Started { get; } = started ?? DateTime.Now;
public DateTime Finished { get; } = finished ?? DateTime.Now;
public string[] Parameters { get; } = parameters ?? [];
public bool Success => RunnerResult == RunnerResult.OK;
public bool HasValue => Returned?.Type != T.Void;
public TimeSpan Elapsed => Finished - Started;
}
public readonly struct Options(
IO.OutputStreamMessageHandler stdOut = null,
IO.InputStreamListenHandler stdIn = null,
int maxCalls = 0x100,
int stackSize = 0x200,
int initialStackSize = 0x80)
{
public readonly IO.OutputStreamMessageHandler StdOut = stdOut;
public readonly IO.InputStreamListenHandler StdIn = stdIn;
public readonly int MaxCalls = maxCalls;
public readonly int StackSize = stackSize;
public readonly int InitialStackSize = initialStackSize;
public Options() : this(null, null, 0x100, 0x200, 0x80) { }
}

View File

@ -1,85 +0,0 @@
using Qrakhen.Qamp.Core.Abstractions;
using Qrakhen.Qamp.Core.Collections;
using Qrakhen.Qamp.Core.Values;
using Qrakhen.Qamp.Core.Values.Objects;
namespace Qrakhen.Qamp.Core.Execution;
using Unsigned = ulong;
using Signed = long;
using Char = char;
using Byte = byte;
using Bool = bool;
using Decimal = double;
public class Segment(
IEnumerable<Instruction> instructions,
IEnumerable<Value> constants) : ISerialize<Segment>
{
public readonly FixedArray<Instruction> Instructions = new(instructions);
public readonly FixedArray<Value> Constants = new(constants);
public byte Read(long offset) => Instructions[offset];
public byte[] Read(long offset, int length)
{
ArgumentOutOfRangeException.ThrowIfGreaterThan(offset + length, Instructions.Length);
byte[] bytes = new byte[length];
for (int i = 0; i < length; i++)
bytes[i] = Instructions[offset + i];
return bytes;
}
/// <summary>
/// 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>
/// <remarks>
/// All of this is done to compress compiled instruction size.
/// Todo: It will need to be tested wheter the cost on performance is stronger than anticipated.
/// </remarks>
public long ReadDynamic(long offset, out int read)
{
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));
public int ReadInt(long offset) => BitConverter.ToInt32(Read(offset, 4));
public long ReadLong(long offset) => BitConverter.ToInt64(Read(offset, 8));
public byte[] Serialize()
{
var opCodes = Instructions.Select(i => (byte)i).ToArray();
var constants = Constants.ToArray();
byte[] bytes = new byte[sizeof(long) + opCodes.Length];
System.Array.Copy(opCodes.LongLength.GetBytes(), 0, bytes, 0, sizeof(long));
System.Array.Copy(opCodes, 0, bytes, sizeof(long), opCodes.Length);
var list = new List<byte>(bytes);
foreach (var constant in constants) {
var data = constant.Signed.GetDynamicBytes();
if (data.Length == 1)
list.Add(data[0]);
else {
list.Add((byte)(data.Length | 0x80));
list.AddRange(data);
}
}
return list.ToArray();
}
public static Segment Deserialize(byte[] data)
{
return null;
}
}

View File

@ -1,102 +0,0 @@
using System.Text;
using System.Text.RegularExpressions;
namespace Qrakhen.Qamp.Core;
public static class Extensions
{
public static uint GetHash(this string str)
{
if (string.IsNullOrEmpty(str))
return 0;
byte[] bytes = Encoding.ASCII.GetBytes(str);
unchecked {
uint hash = 216613661u;
for (int i = 0; i < bytes.Length; i++) {
hash ^= bytes[i];
hash *= 16777619;
}
return hash;
}
}
public static bool TryMatch(this Regex regex, string str, out Match match)
{
match = regex.Match(str);
return match.Success;
}
public static T[] Subset<T>(this T[] array, long from, long length = -1)
{
length = length < 0 ? array.Length - from : length;
var result = new T[length];
Array.Copy(array, from, result, 0, length);
return result;
}
/// <summary>
/// Expensive.
/// </summary>
public static void Insert(this Stream stream, byte[] data, long position = -1)
{
if (position < 0)
position = stream.Position;
byte[] buffer = new byte[stream.Length - position];
if (buffer.Length > 0) {
stream.Position = position;
stream.ReadExactly(buffer);
stream.Write(buffer);
}
stream.Position = position;
stream.Write(data);
}
public static void Remove(this Stream stream, long position, int amount)
{
byte[] buffer = new byte[stream.Length - (position + amount)];
if (buffer.Length > 0) {
stream.Position = position + amount;
stream.ReadExactly(buffer);
stream.Position = position;
stream.Write(buffer);
}
stream.Position = position;
stream.SetLength(stream.Length - amount);
}
public static byte[] GetDynamicBytes(this long value)
{
byte[] data = value.GetBytes();
return data.Subset(0, GetPrimitiveLength(value));
}
public static int GetPrimitiveLength(this long value)
{
int length = 8;
for (int i = 1; i < 8; i++) {
if (value <= (1L << (i * 8))) {
length = i;
break;
}
}
return length;
}
public static byte[] GetBytes(this short value) => BitConverter.GetBytes(value);
public static byte[] GetBytes(this ushort value) => BitConverter.GetBytes(value);
public static byte[] GetBytes(this int value) => BitConverter.GetBytes(value);
public static byte[] GetBytes(this uint value) => BitConverter.GetBytes(value);
public static byte[] GetBytes(this long value) => BitConverter.GetBytes(value);
public static byte[] GetBytes(this ulong value) => BitConverter.GetBytes(value);
public static byte[] GetBytes(this double value) => BitConverter.GetBytes(value);
public static short ToInt16(this byte[] bytes) => BitConverter.ToInt16(bytes);
public static ushort ToUInt16(this byte[] bytes) => BitConverter.ToUInt16(bytes);
public static int ToInt32(this byte[] bytes) => BitConverter.ToInt32(bytes);
public static uint ToUInt32(this byte[] bytes) => BitConverter.ToUInt32(bytes);
public static long ToInt64(this byte[] bytes) => BitConverter.ToInt64(bytes);
public static ulong ToUInt64(this byte[] bytes) => BitConverter.ToUInt64(bytes);
public static double ToDouble(this byte[] bytes) => BitConverter.ToDouble(bytes);
}

View File

@ -1,19 +0,0 @@
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

@ -1,160 +0,0 @@
using System.Diagnostics;
using System.Runtime.CompilerServices;
namespace Qrakhen.Qamp.Core.Logging;
public interface ILogger
{
LogLevel Level { get; set; }
void Log(LogLevel level, params object?[] values);
void Critical(params object?[] values);
void Error(params object?[] values);
void Warn(params object?[] values);
void Info(params object?[] values);
void Debug(params object?[] values);
void Trace(params object?[] values);
void Verbose(params object?[] values);
void Method(object? value = null, string? caller = null);
}
[Flags]
public enum LogLevel
{
Mute = 0x00,
Critical = 0x01,
Error = 0x02,
Warn = 0x04,
Info = 0x08,
Debug = 0x10,
Trace = 0x20,
Verbose = 0x40,
All = Critical | Error | Warn | Info | Debug | Trace | Verbose
}
public interface ILogFormatter
{
string[] Format(object? value, int maxWidth = 80);
}
public interface ILoggerService
{
ILogger Get<T>(string? name = null, LogLevel? level = null);
ILogger Get(string name, LogLevel? level = null);
}
public class LoggerService
{
private static readonly Dictionary<string, ILogger> _loggers = new Dictionary<string, ILogger>();
public static LogLevel Default = LogLevel.All;
public static ILogger Get<T>(string? name = null, LogLevel? level = null)
{
level ??= Default;
string _name = nameof(T);
if (!string.IsNullOrEmpty(name))
_name = $"{_name}:{name}";
return Get(_name, level);
}
public static ILogger Get(string name, LogLevel? level = null)
{
level ??= Default;
ILogger? logger;
if (_loggers.TryGetValue(name, out logger))
return logger;
logger = new Logger(name, level.Value);
_loggers.Add(name, logger);
return logger;
}
}
public class Logger : ILogger, ILogFormatter
{
public string Name { get; private set; }
private LogLevel _level;
public LogLevel Level { get => true ? LoggerService.Default : _level; set => _level = value; }
private static Dictionary<LogLevel, (string First, string Extra)> headers = new() {
{ LogLevel.Critical, (" !? ", " !? ") },
{ LogLevel.Error, (" !! ", " ! ") },
{ LogLevel.Warn, (" !> ", " ! ") },
{ LogLevel.Info, (" :> ", " : ") },
{ LogLevel.Debug, (" > ", " ") },
{ LogLevel.Trace, (" > ", " ") },
{ LogLevel.Verbose, (" > ", " ") },
};
private static Dictionary<LogLevel, ConsoleColor> colors = new()
{
{ LogLevel.Critical, ConsoleColor.DarkRed },
{ LogLevel.Error, ConsoleColor.Red },
{ LogLevel.Warn, ConsoleColor.Yellow },
{ LogLevel.Info, ConsoleColor.White },
{ LogLevel.Debug, ConsoleColor.Gray },
{ LogLevel.Trace, ConsoleColor.DarkGray },
{ LogLevel.Verbose, ConsoleColor.DarkGray },
};
public Logger(string name, LogLevel level)
{
Name = name;
Level = level;
}
public string[] Format(object? value, int maxWidth = 80)
{
List<string> result = [];
if (value == null)
result.Add("null");
else if (value is string str)
result.AddRange(str.Split('\n'));
else if (value is Exception e) {
result.Add(e.Message);
if (!string.IsNullOrEmpty(e.StackTrace))
result.AddRange(e.StackTrace.Split('\n'));
} else
result.Add(value?.ToString() ?? "null");
return result.ToArray();
}
public void Log(LogLevel level, params object?[] values)
{
if ((level & Level) != level)
return;
var header = headers[level];
Console.ForegroundColor = colors[level];
foreach (var value in values) {
var lines = Format(value);
for (int i = 0; i < lines.Length; i++)
IO.Console.Write($"{(i == 0 ? header.First : header.Extra)}{lines[i]}\n");
}
Console.ForegroundColor = ConsoleColor.White;
}
public void Critical(params object?[] values) => Log(LogLevel.Critical, values);
public void Error(params object?[] values) => Log(LogLevel.Error, values);
public void Warn(params object?[] values) => Log(LogLevel.Warn, values);
public void Info(params object?[] values) => Log(LogLevel.Info, values);
public void Debug(params object?[] values) => Log(LogLevel.Debug, values);
public void Trace(params object?[] values) => Log(LogLevel.Trace, values);
public void Verbose(params object?[] values) => Log(LogLevel.Verbose, values);
public void Method(object? value = null, [CallerMemberName] string? caller = null)
{
if (Level < LogLevel.Trace)
return; // save some ticks here
if (caller == null) {
var st = new StackTrace();
var previous = st.GetFrame(1)?.GetMethod();
if (previous != null)
caller = $"{previous.DeclaringType?.Name}.{previous.Name}";
}
Log(LogLevel.Trace, [$"[{caller}] {value}"]);
}
}

View File

@ -1,13 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Version>0.0.12.632</Version>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<BaseOutputPath>..\Build\</BaseOutputPath>
<Platforms>AnyCPU;x64</Platforms>
<DefineConstants>LOG;DEV;XD;SAFE_OPERATIONS</DefineConstants>
</PropertyGroup>
</Project>

View File

@ -1,151 +0,0 @@
# Q&
## About
A single-pass byte code interpreter with the ability to pre-compile executables
into a format that can be efficiently executed by the runtime.
It serves many features, which are listed a bit further down below.
This would have officially been the fourth iteration of my language (sqript4),
but I thought I'd stop with the numbers as people would ask what happened to the
previous three languages.
Q& (or Qamp for Q Ampersand) was originally made for my contributions to the
Advent Of Code 2025, but I thought it's a nice and stable enough concept to be
sharing it to the public anyway.
## Usage
To build **Q&**, you need nothing more than a C# compiler that supports .NET10.
There are .NET8 compatible versions available, but they are not recommended,
as **Q&** utilizes all of the performance optimizations .NET10 has to offer.
After cloning, a simple `dotnet build ./` in the root directory will suffice.
You can execute pre-compiled .sqi files using `qamp.runtime.exe <filepath> [parameters]`,
or the REPL/CLI using `qamp.cli.exe [parameters]`.
If you want to build your own source into pre-compiled instruction files,
use `qamp.digest.exe <dirpath>` - the digester will look for an `Init()` function
declared in any of the files, and build the instructions from there.
## Language & Syntax
You are free to choose between the classic sqript-style dialect `*~ q <~ 0xf;` or
the standard dialect, which is strongly inspired by the C# standard: `var q = 0xf;`.
All of the following examples are written in the classic dialect `--dialect=sqr` or `-d=q`.
### Syntax
Most basic Usage:
```cs
# this is a comment.
*~ q <~ 12; # q == 12
*~ f <~ (n) <: n < 2 ? n : f(n-1) + f(n-2); # fibonacci
f(12); # 233
*~ a <~ [1, 2, 3]; # a = [1, 2, 3]
*~ x <~ a:0 + a:2; # x == 4
```
### Assignments
**Q&** has two distinct ways to assign a value to a variable:
- By reference `<&`, or
- By value `<=`.
Using the auto-assignment operator `<~`, **Q&** will choose the correct assignment based on what
type the right-hand expression consists of.
Primitive types will be assigned by value, any object values will be assigned by reference.
This is strongly inspired by the behaviour that most interpeters implement.
#### What happens if...
##### I assign an object by value?
The entire object will be deep-copied and transfered to the new location.
See it as a free clone method on basically anything that is considered an object.
##### I assign a primitive by reference?
So, assuming that `x` is a primitive (value type), given the instruction `*~ y <& x;`,
`y` will be assigned as a reference value _pointing_ to the location of `x` - somewhat like a
pointer in C, but a little smarter.
##### I do not praise the god emperor of mankind?
You will be duly punished.
#### Variable Declaration
```
*~ q <~ 0xf;
```
#### Variable Declaration
#### Types
```
sig a; signed
uns b; unsigned
dec c; decimal
str d; string
chr e; character
bin f; boolean or binary, you can declare bitfields with:
bin:16 bits; custom bitfield type with 16 bits.
ref g; reference type, can be explicitely declared:
ref:sig aRef <& a;
ptr p; pointer, essentially the same as a ref, but in Q& ptr is the base type of all objects.
CustomType yourObject; self-explanatory
any type may be declared as [] array,
{} dictionary, or [:] list. (non-fixed collection)
```
#### Operators, Keywords & their aliases
```
*~ var
:~ const
<: return
:> continue
<+ increment / add
<- subtract / remove
:: print
?: typeof
<& assign reference
^~ base
.~ this
~( function
```
## Customization & Extras
Some more information about extending and customizing Q& to your needs.
### Writing Extension Methods
asdasd
### Implementing external Libraries
Implementation is easy.
Q& is written in C#, and supports basically anything you can wrap in C#.
All you have to do in order to implement a library is referencing the SDK dll
which should be located within your `./Build/` folder after building as described above.
Here's a quick example of how you would implement virtually any library:
```cs
using Qrakhen.Qamp.Core;
using System.Math;
Value Sqrt(Value number)
{
Assert.IsNumber(number);
if (number.IsDecimal)
return Math.Sqrt(number.AsDecimal);
if (number.IsSigned)
return Math.Sqrt(number.AsSigned);
if (number.IsUnsigned)
return Math.Sqrt(number.Unsigned);
throw new Qrakhen.Qamp.Core.QampException($"Unsupported value type {value}");
}
Injector injector = new();
injector.Register(scope: Scope.Global, name: "sqrt", params: [ ("number", ValueType.Number) ], returns: ValueType.Number);
injector.Export("desiredPath.sqi");
```
That's it - although this being a very simple example, more is possible.
I will add a few basic implementations of very common libraries in a separate repository soon.

View File

@ -1,109 +0,0 @@
namespace Qrakhen.Qamp.Core.Tokenization;
using Qrakhen.Qamp.Core.Collections;
using static TokenType;
public class Dialect
{
// suuuper slow but meh, who cares
private Register<string, TokenType> _register = new();
public void Define(TokenType type, string sequence)
=> _register[sequence] = type;
public TokenType Get(string sequence)
=> _register[sequence];
}
public class DefaultDialect : Dialect
{
public DefaultDialect()
{
Define(Null, "null");
Define(GroupOpen, "(");
Define(GroupClose, ")");
Define(ContextOpen, "{");
Define(ContextClose, "}");
Define(ArrayOpen, "[");
Define(ArrayClose, "]");
Define(Comma, ",");
Define(Dot, ".");
Define(Colon, ":");
Define(Semicolon, ";");
Define(Minus, "-");
Define(Plus, "+");
Define(MinusEqual, "-=");
Define(PlusEqual, "+=");
Define(Decrement, "++");
Define(Increment, "--");
Define(Slash, "/");
Define(SlashEqual, "/=");
Define(Star, "*");
Define(StarEqual, "*=");
Define(Modulo, "%");
Define(ModuloEqual, "%=");
Define(BitwiseAnd, "&");
Define(BitwiseOr, "|");
Define(BitwiseXor, "^");
Define(BitwiseNot, "~");
Define(BitwiseLeft, "<<");
Define(BitwiseRight, ">>");
Define(Bang, "!");
Define(BangEqual, "!=");
Define(Equal, "=");
Define(EqualEqual, "==");
Define(Greater, ">");
Define(GreaterEqual, ">=");
Define(Less, "<");
Define(LessEqual, "<=");
Define(Question, "?");
Define(DoubleQuestion, "??");
Define(Identifier, "([_a-zA-Z][_a-zA-Z0-9]+?)");
Define(String, "(:?('.+')|\".+\")");
Define(Integer, "(\\d+)");
Define(Decimal, "");
Define(Hexadecimal, "0x([a-fA-F0-9]+)");
Define(And, "&&");
Define(Else, "else");
Define(False, "false");
Define(For, "for");
Define(If, "if");
Define(Or, "||");
Define(This, "this");
Define(True, "true");
Define(Var, "var");
Define(While, "while");
Define(Do, "do");
Define(Return, "return");
Define(Ref, "ref");
Define(Function, "function");
Define(Class, "class");
Define(Base, "base");
Define(TypeOf, "typeof");
Define(Print, "print");
Define(Import, "import");
Define(Export, "export");
}
}
public class ClassicDialect : DefaultDialect
{
public ClassicDialect()
{
Define(Null, "_");
Define(PlusEqual, "<+");
Define(MinusEqual, "<-");
Define(Equal, "<~");
Define(Ref, "&");
Define(This, ".~");
Define(Var, "*~");
Define(Return, "<:");
Define(Ref, "*&");
Define(Function, "fq");
Define(Base, "^~");
Define(TypeOf, "?:");
Define(Print, "::");
Define(Import, "<!");
Define(Export, "!>");
}
}

View File

@ -1,393 +0,0 @@
using Qrakhen.Qamp.Core.Collections.Abstractions;
using Qrakhen.Qamp.Core.Logging;
using System.Text;
using System.Text.RegularExpressions;
using static Qrakhen.Qamp.Core.Tokenization.TokenType;
namespace Qrakhen.Qamp.Core.Tokenization;
internal static partial class ReaderPatterns
{
[GeneratedRegex(@"((?:\d+)?\.(?:\d+))")]
public static partial Regex IsDecimal();
[GeneratedRegex(@"(\d+)")]
public static partial Regex IsInteger();
[GeneratedRegex(@"0x([a-fA-F0-9]+)")]
public static partial Regex IsHexadecimal();
public static readonly Dictionary<string, TokenType> Keywords = new();
public static readonly Dictionary<string, string> Aliases = new();
private static void Define(string key, TokenType type)
{
// not ideal, but momentarily better than setting up complex dialects
if (Keywords.ContainsValue(type))
Aliases[key] = Keywords.First(v => v.Value == type).Key;
Keywords[key] = type;
}
static ReaderPatterns()
{
Define("false", False);
Define("true", True);
Define("null", Null);
Define("void", Null);
Define("and", And);
Define("else", Else);
Define("for", For);
Define("if", If);
Define("or", Or);
Define("this", This);
Define(".~", This);
Define("var", Var);
Define("*~", Var);
Define("while", While);
Define("do", Do);
Define("ref", Ref);
Define("function", Function);
Define("funqtion", Function);
Define("fq", Function);
Define("funq", Function);
Define("return", Return);
Define("<:", Return);
Define("class", Class);
Define("base", Base);
Define("^~", Base);
Define("typeof", TypeOf);
Define("?:", TypeOf);
Define("print", Print);
Define("::", Print);
Define("globals", PrintGlobals);
Define("stack", PrintStack);
Define("expr", PrintExpr);
Define("import", Import);
Define("export", Export);
}
}
public class Reader : IReader<Token>, IDisposable
{
private const char EofByte = (char)0xFF;
private const int ChunkSize = 0x20;
private readonly Stream _stream;
private readonly Tokens _tokens;
private readonly List<char> _buffer;
private readonly ILogger _logger = LoggerService.Get<Reader>();
private int _line = 0;
private int _column = 0;
private int _current = 0;
private long _startIndex = 0;
private TokenPosition _startPosition = default;
public bool Done => _stream.Position >= _stream.Length &&
_current >= _buffer.Count;
public TokenPosition CurrentPosition => new TokenPosition(_line, _column);
public Reader(Stream stream)
{
_stream = stream;
_stream.Position = 0;
_tokens = new Tokens();
_buffer = [];
}
public void Dispose()
{
_stream.Dispose();
}
public Token NextToken(bool includeCompilationIrrelevant = false)
{
if (Done)
return Token.Eof();
Token token = ReadToken();
while (!includeCompilationIrrelevant && token.IsCompilationIrrelevant && !Done)
token = ReadToken();
if (!includeCompilationIrrelevant && token.IsCompilationIrrelevant && Done)
return Token.Eof();
return token;
}
private char Next()
{
if (!Done && _current >= _buffer.Count)
Continue();
if (Done)
return EofByte;
_column++;
return _buffer[_current++];
}
private char Peek(int delta = 0)
{
if (_current + delta >= _buffer.Count)
Continue();
if (_current + delta >= _buffer.Count)
return EofByte;
return _buffer[_current + delta];
}
private Token ReadToken()
{
_startIndex = _current;
_startPosition = CurrentPosition;
string buffer = string.Empty;
char c = Next();
buffer += c;
if (c == '\n' || c == '\r') {
var position = CurrentPosition;
if (c == '\r')
buffer += Next();
_line++;
_column = 0;
return MakeToken(NewLine, buffer);
}
if (c == '#') {
var position = CurrentPosition;
do {
c = Next();
buffer += c;
} while (c != '\n' && c != EofByte);
return MakeToken(Comment, buffer);
}
if (c == ' ') {
var position = CurrentPosition;
while (Match(' '))
buffer += ' ';
return MakeToken(Whitespace, buffer);
}
if (c is '\'' or '"') {
string str = ReadUntil(buffer, c);
if (str[^1] != c || str.Length < 2)
return MakeToken(Error, str);
return MakeToken(TokenType.String, str.Substring(1, str.Length - 2));
}
if (IsDigit(c) || (c == '.' && IsDigit(Peek()))) {
do {
c = Peek(0);
if (IsDigit(c) || IsHex(c) || c == 'x' || c == '.')
buffer += Next();
else
break;
} while (c != EofByte);
if (ReaderPatterns.IsDecimal().TryMatch(buffer, out Match match))
return MakeToken(TokenType.Decimal, match.Groups[1].Value);
if (ReaderPatterns.IsHexadecimal().TryMatch(buffer, out match))
return MakeToken(Hexadecimal, match.Groups[1].Value);
if (ReaderPatterns.IsInteger().TryMatch(buffer, out match))
return MakeToken(Integer, match.Groups[1].Value);
throw new ReaderException($"Unrecognizable number detected <{buffer}>", this);
}
if (IsLetter(c)) {
do {
c = Peek();
if (IsLetter(c))
buffer += Next();
else
break;
} while (c != EofByte);
return MakeIdentifier(buffer);
}
return MakeOperator(buffer);
}
private string ReadUntil(string buffer, char until)
{
char c;
do {
c = Next();
if (c == EofByte)
return buffer;
/*throw new ReaderException(
$"Unexpected end of feed while trying to read string <{buffer}>",
this);*/
buffer += c;
} while (c != until || Peek(-2) == '\\');
return buffer;
}
private Token MakeIdentifier(string buffer)
{
if (ReaderPatterns.Keywords.TryGetValue(buffer, out TokenType type))
return MakeToken(type, buffer);
return MakeToken(Identifier, buffer);
}
private bool Check(char c) => Peek(0) == c;
private bool Match(char c)
{
if (!Check(c))
return false;
Next();
return true;
}
private bool MatchSequence(char[] sequence, string buffer, TokenType type, out Token token)
{
token = Token.Void;
string _buffer = buffer;
for (int i = 0; i < sequence.Length; i++) {
char c = buffer.Length > i ? buffer[i] : Peek(i - buffer.Length);
if (sequence[i] != c)
return false;
_buffer += c;
}
token = MakeToken(type, _buffer);
return true;
}
private bool IsLetter(char c) => c is >= 'a' and <= 'z' or >= 'A' and <= 'Z' or '_';
private bool IsDigit(char c) => c is >= '0' and <= '9';
private bool IsHex(char c) => c is >= 'a' and <= 'f' or >= 'A' and <= 'F' || IsDigit(c);
private Token MakeOperator(string buffer)
{
return buffer[0] switch {
'[' => MakeToken(ArrayOpen, buffer),
']' => MakeToken(ArrayClose, buffer),
'{' => MakeToken(ContextOpen, buffer),
'}' => MakeToken(ContextClose, buffer),
'(' => MakeToken(GroupOpen, buffer),
')' => MakeToken(GroupClose, buffer),
'.' => Check('~') ?
MakeToken(This, buffer + Next()) :
MakeToken(Dot, buffer),
',' => MakeToken(Comma, buffer),
';' => MakeToken(Semicolon, buffer),
':' => Check(':') ?
MakeToken(Print, buffer + Next()) :
Check('[') ?
MakeToken(ListOpen, buffer + Next()) :
MakeToken(Colon, buffer),
'&' => Check('&') ?
MakeToken(And, buffer + Next()) :
Check(':') ?
MakeToken(AddressOf, buffer + Next()) :
MakeToken(BitwiseAnd, buffer),
'^' => Check('~') ?
MakeToken(Base, buffer + Next()) :
MakeToken(BitwiseXor, buffer),
'%' => Check('=') ?
MakeToken(ModuloEqual, buffer + Next()) :
MakeToken(Modulo, buffer),
'|' => Check('|') ?
MakeToken(Or, buffer + Next()) :
MakeToken(BitwiseOr, buffer),
'!' => Check('=') ?
MakeToken(BangEqual, buffer + Next()) :
MakeToken(Bang, buffer),
'+' => Check('+') ?
MakeToken(Increment, buffer + Next()) :
Check('=') ?
MakeToken(PlusEqual, buffer + Next()) :
MakeToken(Plus, buffer),
'-' => Check('-') ?
MakeToken(Decrement, buffer + Next()) :
Check('=') ?
MakeToken(MinusEqual, buffer + Next()) :
MakeToken(Minus, buffer),
'/' => Check('=') ?
MakeToken(SlashEqual, buffer + Next()) :
MakeToken(Slash, buffer),
'*' => Check('~') ?
MakeToken(Var, buffer + Next()) :Check('=') ?
MakeToken(StarEqual, buffer + Next()) :
Check('*') ?
MakeToken(Pow, buffer + Next()) :
MakeToken(Star, buffer),
'=' => Check('=') ?
MakeToken(EqualEqual, buffer + Next()) :
MakeToken(Equal, buffer),
'<' => Peek(0) switch {
'~' => MakeToken(Equal, buffer + Next()),
':' => MakeToken(Return, buffer + Next()),
'+' => MakeToken(PlusEqual, buffer + Next()),
'-' => MakeToken(MinusEqual, buffer + Next()),
'<' => MakeToken(BitwiseLeft, buffer + Next()),
'=' => MakeToken(LessEqual, buffer + Next()),
'>' => MakeToken(TernaryElse, buffer + Next()),
_ => MakeToken(Less, buffer)
},
'>' => Check('>') ?
MakeToken(BitwiseRight, buffer + Next()) :
Check('=') ?
MakeToken(GreaterEqual, buffer + Next()) :
MakeToken(Greater, buffer),
'~' => Check('(') ?
MakeToken(Lambda, buffer) :
MakeToken(BitwiseNot, buffer),
'?' => Check('?') ?
MakeToken(DoubleQuestion, buffer + Next()) :
Check(':') ?
MakeToken(TypeOf, buffer + Next()) :
MakeToken(Question, buffer),
_ => MakeToken(Error, buffer) //throw new ReaderException($"Could not identify operator <{buffer}>", this)
};
}
private Token MakeToken(TokenType type, string buffer)
{
return new Token(
type,
buffer,
_startPosition,
new StreamSpan(_startIndex, _current - _startIndex));
}
private void Continue()
{
if (Done)
return;
long size = _stream.Length - _stream.Position;
if (size > ChunkSize)
size = ChunkSize;
byte[] buffer = new byte[size];
int read = _stream.Read(buffer, 0, (int)size);
if (read < 0)
return;
_buffer.AddRange(Encoding.UTF8.GetChars(buffer));
}
private readonly struct CaptureHandle(long start)
{
public readonly long Start = start;
}
}

View File

@ -1,42 +0,0 @@
namespace Qrakhen.Qamp.Core.Tokenization;
public readonly record struct Token(
TokenType Type,
string? Value = "",
TokenPosition Position = default,
StreamSpan Span = default
)
{
public static Token Void = new(TokenType.Void);
public static Token Error(string? message = null) => new Token(TokenType.Error, message);
public static Token Eof(string? message = null) => new Token(TokenType.Eof, message);
public override string ToString()
{
return $"[@{Position}] <{Type}> ({Value ?? "?"})";
}
public bool IsCompilationIrrelevant => Type == TokenType.NewLine || Type == TokenType.Comment || Type == TokenType.Whitespace;
}
public readonly record struct StreamSpan(long Start = -1, long Length = 0);
public readonly record struct TokenPosition(int Line = -1, int Column = -1)
{
public static readonly TokenPosition None = new(-1, -1);
public bool IsNone => this == None;
public override string ToString()
{
return $"{Line}:{Column}";
}
public TokenPosition Previous => new(Line, Column - 1);
public static TokenPosition operator +(TokenPosition a, TokenPosition b) =>
new(a.Line + b.Line, a.Column + b.Column);
public static TokenPosition operator -(TokenPosition a, TokenPosition b) =>
new(a.Line - b.Line, a.Column - b.Column);
}

View File

@ -1,136 +0,0 @@
namespace Qrakhen.Qamp.Core.Tokenization;
using static TokenType;
[Flags]
public enum TokenType
{
Error = -1,
Void = 0,
Constant = 1 << 4,
Null = Constant | 1,
True = Constant | 2,
False = Constant | 3,
Format = 1 << 5,
Eof = Format | 1,
NewLine = Format | 2,
Whitespace = Format | 3,
Comment = Format | 4,
Bracket = 1 << 6,
GroupOpen = Bracket | 1,
GroupClose = Bracket | 2,
ContextOpen = Bracket | 3,
ContextClose = Bracket | 4,
ArrayOpen = Bracket | 5,
ArrayClose = Bracket | 6,
ListOpen = Bracket | 7,
Lambda = Bracket | 8,
Structure = 1 << 7,
Comma = Structure | 1,
Dot = Structure | 2,
Colon = Structure | 3,
Semicolon = Structure | 4,
Operator = 1 << 8,
Minus = Operator | 1,
Plus = Operator | 2,
Slash = Operator | 3,
Star = Operator | 4,
Modulo = Operator | 5,
Pow = Operator | 6,
BitwiseAnd = Operator | 7,
BitwiseOr = Operator | 8,
BitwiseXor = Operator | 9,
BitwiseNot = Operator | 10,
BitwiseLeft = Operator | 11,
BitwiseRight = Operator | 12,
AddressOf = Operator | 13, // returns the address of a ptr/object as a value
Assignment = 1 << 9,
Equal = Assignment | 1,
PlusEqual = Assignment | 2,
MinusEqual = Assignment | 3,
SlashEqual = Assignment | 4,
StarEqual = Assignment | 5,
ModuloEqual = Assignment | 6,
Decrement = Assignment | 7,
Increment = Assignment | 8,
Condition = 1 << 10,
Bang = Condition | 1,
BangEqual = Condition | 2,
EqualEqual = Condition | 4,
Greater = Condition | 5,
GreaterEqual = Condition | 6,
Less = Condition | 7,
LessEqual = Condition | 8,
And = Condition | 9,
Or = Condition | 10,
Literal = 1 << 11,
Identifier = Literal | 1,
String = Literal | 2,
Integer = Literal | 3,
Decimal = Literal | 4,
Hexadecimal = Literal | 5,
Control = 1 << 12,
Else = Control | 1,
For = Control | 2,
If = Control | 3,
This = Control | 4,
Var = Control | 5,
While = Control | 6,
Do = Control | 7,
Return = Control | 8,
Question = Control | 9,
DoubleQuestion = Control | 10,
TernaryElse = Control | 11,
Ref = Control | 12,
Function = Control | 13,
Class = Control | 14,
Base = Control | 15,
Native = 1 << 14,
Print = Native | 1,
PrintStack = Native | 2,
PrintGlobals = Native | 3,
PrintExpr = Native | 4,
TypeOf = Native | 5,
Import = Native | 6,
Export = Native | 7
}
public static class TokenTypeExtensions // iknow, i know, gotta make this flagged rather than THIS
{
public static bool IsBracket(this TokenType type)
=> type is GroupClose or GroupOpen or ContextClose or ContextOpen or ArrayClose or ArrayOpen or ListOpen or Lambda;
public static bool IsString(this TokenType type)
=> type is TokenType.String;
public static bool IsNumber(this TokenType type)
=> type is Integer or Decimal;
public static bool IsIdentifier(this TokenType type)
=> type is Identifier;
public static bool IsControl(this TokenType type)
=> type is If or Var or Class or Else or For or While or Do or Return or And or Or or TypeOf or Print;
public static bool IsBoolean(this TokenType type)
=> type is True or False;
public static bool IsOperator(this TokenType type)
=> !type.IsBracket() &&
!type.IsString() &&
!type.IsNumber() &&
!type.IsIdentifier() &&
!type.IsControl() &&
!type.IsBoolean();
}

View File

@ -1,21 +0,0 @@
using Qrakhen.Qamp.Core.Abstractions;
namespace Qrakhen.Qamp.Core.Tokenization;
public class Tokens : IDebug<string>
{
private readonly List<Token> _tokens = new();
public Token[] GetTokens() => _tokens.ToArray();
public void Feed(Token token) => _tokens.Add(token);
public string Debug(DebugLevel level = DebugLevel.None)
{
string str = Debugger.GetContextString(this);
foreach (var token in _tokens) {
str += $"\n {token}";
}
return str;
}
}

View File

@ -1,14 +0,0 @@
using System.Runtime.InteropServices;
namespace Qrakhen.Qamp.Core.Values;
[StructLayout(LayoutKind.Sequential, Size = sizeof(long))]
public readonly record struct Address(long Value)
{
public static readonly Address Void = new Address(-1);
public override string ToString() => Value < 0 ? "&void" : $"&{Value:x8}";
public static implicit operator long(Address address) => address.Value;
public static implicit operator Address(long value) => new(value);
}

View File

@ -1,268 +0,0 @@
using Qrakhen.Qamp.Core.Collections;
using Qrakhen.Qamp.Core.Logging;
using Qrakhen.Qamp.Core.Values.Objects;
using System.Linq.Expressions;
using System.Reflection;
namespace Qrakhen.Qamp.Core.Values;
// todo: do the same for native methods. make it more abstract. i like the approach.
public delegate Value ExtensionDelegate(Value target, Value[] parameters);
public class ExtensionException(string message, object? context = null) : QampException(message, context);
public abstract class ExtensionAttribute : Attribute;
public abstract class TypedExtensionAttribute : ExtensionAttribute
{
public ValueType ValueType { get; set; } = ValueType.Undefined;
public Type? ValueObjectType { get; set; } = null;
public string? ValueObjectName { get; set; }
public bool Nullable { get; set; } = false;
public TypeInfo TypeInfo => new TypeInfo(ValueType, ValueObjectType, ValueObjectName, Nullable);
}
[AttributeUsage(AttributeTargets.Method)]
public class ExtensionMethodAttribute : ExtensionAttribute
{
public string? Name { get; set; } = null;
}
[AttributeUsage(AttributeTargets.Method)]
public class ReturnsAttribute : TypedExtensionAttribute;
[AttributeUsage(AttributeTargets.Parameter)]
public class SelfAttribute : TypedExtensionAttribute;
[AttributeUsage(AttributeTargets.Class)]
public class ExtensionClassAttribute : SelfAttribute
{
public string? Name { get; set; } = null;
}
[AttributeUsage(AttributeTargets.Parameter)]
public class ParameterAttribute : TypedExtensionAttribute
{
public string? Name { get; set; } = null;
public bool Optional { get; set; } = false;
}
public class ExtensionMethod(
TypeInfo targetType,
TypeInfo returnType,
string name,
ExtensionDelegate callback,
string[] parameters)
{
private static readonly Register<TypeInfo, Register<string, ExtensionMethod>> _register = [];
private static readonly ILogger _logger = LoggerService.Get<ExtensionMethod>();
public readonly string Name = name;
public readonly TypeInfo TargetType = targetType;
public readonly TypeInfo ReturnType = returnType;
public readonly ExtensionDelegate Callback = callback;
public readonly string[] Parameters = parameters.ToArray();
public readonly string Key = parameters.Length > 0 ? $"{name}_{parameters.Length}" : name;
public static ExtensionMethod? Get(TypeInfo targetType, string name, int parameters = 0)
{
if (parameters > 0)
name = $"{name}_{parameters}";
if (!_register.Has(targetType))
return null;
if (!_register[targetType].Has(name))
return null;
return _register[targetType][name];
}
public static void Register(IEnumerable<ExtensionMethod> extensions)
{
foreach (var extension in extensions)
Register(extension);
}
public static void Register(ExtensionMethod extension)
{
if (!_register.Has(extension.TargetType))
_register.Add(extension.TargetType, []);
if (_register[extension.TargetType].Has(extension.Key))
throw new ExtensionException($"Extension {extension.Key} already exists for type {extension.TargetType}.");
_register[extension.TargetType].Add(extension.Key, extension);
}
public static void Register(TypeInfo targetType,
TypeInfo returnType,
string name,
ExtensionDelegate callback,
params string[] parameters)
=> Register(new ExtensionMethod(targetType, returnType, name, callback, parameters));
private static ExtensionMethod RenderExtension(MethodInfo method)
{
_logger.Verbose($"Rendering extension from {method}...");
var attr = method.GetCustomAttribute<ExtensionMethodAttribute>();
if (attr is null)
throw new ExtensionException($"Expected an [ExtensionMethod] attribute on method {method} for extension compilation.", method);
var parameters = method.GetParameters();
if (parameters.Length < 1)
throw new ExtensionException($"Tried to define method {method} as extension, but extensions require their first parameter to be <self>, the target value of the extension.", method);
var selfAttr = parameters[0].GetCustomAttribute<SelfAttribute>() ?? method.DeclaringType?.GetCustomAttribute<ExtensionClassAttribute>();
if (selfAttr is null)
throw new ExtensionException($"Expected first parameter of {method} to have a [Self], or its declaring type to have a [ExtensionClass] attribute in order to define the extension's target type.");
TypeInfo anyType = TypeInfo.Any;
string name = attr.Name ?? method.Name;
TypeInfo selfType = selfAttr.TypeInfo;
TypeInfo returnType = method.GetCustomAttribute<ReturnsAttribute>()?.TypeInfo ?? anyType; // todo: assume returned type from actual return value
string[] parameterNames = parameters.Subset(1).Select(p => p.Name!).ToArray() ?? []; // todo: use the actual parameter attribute for this.
ExtensionDelegate callback = RenderDelegate(method);
return new ExtensionMethod(selfType, returnType, name, callback, parameterNames);
}
private static ExtensionDelegate RenderDelegate(MethodInfo method)
{
_logger.Verbose($"Rendering delegate expression...");
var selfParameter = Expression.Parameter(typeof(Value), "self");
var argsParameter = Expression.Parameter(typeof(Value[]), "args");
ParameterInfo[] parameterInfos = method.GetParameters();
var parameterExpressions = new Expression[parameterInfos.Length - 1];
for (int i = 0; i < parameterExpressions.Length; i++) {
ConstantExpression index = Expression.Constant(i);
BinaryExpression indexExpression = Expression.ArrayIndex(argsParameter, index);
/* todo: unsure
Type requiredType = parameterInfos[i].ParameterType;
if (indexExpression.Type != requiredType) {
parameterExpressions[i] = Expression.Convert(indexExpression, requiredType);
}*/
parameterExpressions[i] = indexExpression;
}
Expression[] arguments = [selfParameter, .. parameterExpressions];
MethodCallExpression callExpression = Expression.Call(method, arguments);
Expression final;
if (method.ReturnType != typeof(Value)) {
ConstructorInfo? ctor = typeof(Value).GetConstructor([ method.ReturnType ]);
if (ctor is null)
throw new ExtensionException($"Can not compile extension from method {method}, its return type '{method.ReturnType}' is not accepted by any Value constructors.", method);
final = Expression.New(ctor, callExpression);
} else {
final = callExpression;
}
var lambda = Expression.Lambda<ExtensionDelegate>(final, selfParameter, argsParameter);
_logger.Verbose($"Done: ", lambda);
return lambda.Compile();
}
public static IEnumerable<ExtensionMethod> CompileExtensionsFromType(Type type)
{
_logger.Debug($"Compiling static extension methods from {type}...");
List<ExtensionMethod> extensions = [];
var methods = type.GetMethods(BindingFlags.Static | BindingFlags.Public)
.Where(method => method.GetCustomAttribute<ExtensionMethodAttribute>() is not null)
.ToArray();
if (methods.Length == 0) {
_logger.Warn($"Tried to compile static extension methods from {type}, but no public static methods were found declared inside it.");
return extensions;
}
foreach (var method in methods) {
extensions.Add(RenderExtension(method));
}
return extensions;
}
static ExtensionMethod()
{
Register(CompileExtensionsFromType(typeof(StringExtensions)));
}
public Value Call(Value self, Value[] parameters)
{
return Callback(self, parameters);
}
public override string ToString()
{
return $"{TargetType}.{Name}({string.Join(", ", Parameters)}) (NativeExtension)";
}
}
[ExtensionClass(ValueType = ValueType.String)]
public static class StringExtensions
{
[ExtensionMethod]
public static long Length(Value self) // make this possible with Objects.String and auto-detection
{
return self.Ptr.As<Objects.String>()?.Value?.Length ?? 0;
}
[ExtensionMethod]
public static long IndexOf(Value self, Value needle)
{
string _self = self.Ptr.As<Objects.String>()?.Value ?? "";
string _needle = needle.Ptr.As<Objects.String>()?.Value ?? "";
if (_needle.Length < 1 || _self.Length < 1)
return -1;
return _self.IndexOf(_needle);
}
[ExtensionMethod]
public static Value SubString(Value self, Value start, Value length)
{
return self.Ptr.As<Objects.String>()?.SubString(start, length) ?? Value.Void;
}
[ExtensionMethod]
public static Objects.Array Split(Value self, Value splitter)
{
string? _splitter = splitter.Ptr.As<Objects.String>()?.Value;
string? _string = self.Ptr.As<Objects.String>()?.Value;
if (_string is null)
return new Objects.Array([]);
if (_splitter is null)
return new Objects.Array([ self ]);
string[] parts = _string.Split(_splitter);
Value[] values = new Value[parts.Length];
for (int i = 0; i < parts.Length; i++)
values[i] = Objects.String.Make(parts[i]);
return new Objects.Array(values);
}
}
[ExtensionClass(ValueType = ValueType.Array)]
public static class ArrayExtensions
{
[ExtensionMethod]
public static long Length(Objects.Array self)
{
return self.Data.LongLength;
}
}

View File

@ -1,199 +0,0 @@
using Qrakhen.Qamp.Core.Values.Objects;
using System.Reflection;
namespace Qrakhen.Qamp.Core.Values.Native;
public class NativeMemberAttribute(string? name = null) : Attribute
{
public string? Name { get; } = name;
}
public class NativeMember(string name, MemberInfo info)
{
public string Name { get; } = name;
public MemberInfo MemberInfo { get; } = info;
}
public class NativeProperty(string name,
PropertyInfo info,
bool readOnly) : NativeMember(name, info)
{
public bool ReadOnly { get; } = readOnly;
public PropertyInfo PropertyInfo => (MemberInfo as PropertyInfo)!;
}
public class NativeMethod(string name,
MethodInfo info) : NativeMember(name, info)
{
public MethodInfo MethodInfo => (MemberInfo as MethodInfo)!;
}
public static class NativeLinker
{
private static readonly Dictionary<Type, Dictionary<string, NativeMember>> _linked = [];
private static Dictionary<string, NativeMember> GetLinkTable<T>() => GetLinkTable(typeof(T));
private static Dictionary<string, NativeMember> GetLinkTable(Type type)
{
if (!_linked.TryGetValue(type, out Dictionary<string, NativeMember>? table))
{
table = Compile(type);
}
if (table == null)
{
throw new QampException($"{type.Name} has no native members that could be accessed.");
}
return table;
}
private static M GetNativeMember<T, M>(string name) where M : NativeMember
=> GetNativeMember<M>(typeof(T), name);
private static T GetNativeMember<T>(Type type, string name)
{
if (GetLinkTable(type).TryGetValue(name, out NativeMember? member)) {
if (member is T typedMember) {
return typedMember;
}
}
throw new QampException($"{type.Name} has no member {name} that could be accessed.");
}
public static Value GetProperty<TObj>(TObj obj, string name)
{
var property = GetNativeMember<TObj, NativeProperty>(name);
return Value.Void; //new Value(property.PropertyInfo.GetValue(obj));
}
public static void SetProperty<TObj>(TObj obj, string name, Value value)
{
}
public static Value CallMember<TObj>(TObj obj, string name, Value[] args)
{
var method = GetNativeMember<TObj, NativeMethod>(name);
return Value.Void;
}
private static void Compile(Type type, string method, int argCount)
{
}
private static void Compile(Type type, string member)
{
}
private static Dictionary<string, NativeMember> Compile(Type type)
{
var members = type.GetMembers();
var table = new Dictionary<string, NativeMember>();
foreach (var member in members)
{
var attr = member.GetCustomAttribute<NativeMemberAttribute>();
if (attr == null)
continue;
NativeMember? native = null;
if (member is PropertyInfo propertyInfo)
{
native = new NativeProperty(attr.Name ?? member.Name,
propertyInfo,
propertyInfo.CanWrite);
}
if (member is MethodInfo methodInfo)
{
native = new NativeMethod(attr.Name ?? methodInfo.Name,
methodInfo);
}
if (native == null)
throw new Exception("meh.");
table[native.Name] = native;
}
return _linked[type] = table;
}
}
/*
public delegate Value Getter(Obj? self);
public delegate void Setter(Obj? self, Value value);
public delegate Value Method(Obj? self, Value[] args);
public class NativeMember(string name, bool isStatic)
{
public readonly string Name = name;
public readonly bool IsStatic = isStatic;
}
public class NativeConst(string name, Value value)
: NativeMember(name, true)
{
public readonly Value Value = value;
}
public class NativeGetter(string name, bool isStatic, Getter getter)
: NativeMember(name, isStatic)
{
public readonly Getter Getter = getter;
}
public class NativeSetter(string name, bool isStatic, Getter getter, Setter setter)
: NativeGetter(name, isStatic, getter)
{
public readonly Setter Setter = setter;
}
public class NativeMethod(string name, bool isStatic, Method method)
: NativeMember(name, isStatic)
{
public readonly Method Method = method;
}
public static class Natives
{
private static readonly Dictionary<Type, Dictionary<string, NativeMember>> _members = [];
private static readonly Dictionary<Type, Dictionary<string, NativeMember>> _membersStatic = [];
private static Dictionary<string, NativeMember> Prepare(Type type, string name, bool isStatic)
{
if (!type.IsAssignableTo(typeof(Obj)))
throw new QampException($"Only object types may have native members assigned. The provided type {type} can not have any attached properties or members. Use extensions to handle that type.");
var target = isStatic ? _membersStatic : _members;
if (!target.ContainsKey(type))
target.Add(type, []);
if (target[type].ContainsKey(name))
throw new QampException($"Can not register native {(isStatic ? "static" : "")} member {name} for {type}, that member was already defined.");
return target[type];
}
public static void Register(Type type, NativeMember member)
{
var target = Prepare(type, member.Name, member.IsStatic);
target[member.Name] = member;
}
public static void Register(Type type, string name, Value constant)
{
Register(type, new NativeConst(name, constant));
}
public static void Register(Type type, MethodInfo info, Method method)
{
Register(type, new NativeMethod(info.Name, info.IsStatic, method));
}
}*/

View File

@ -1,39 +0,0 @@
using System;
namespace Qrakhen.Qamp.Core.Values.Objects;
public class Array(IEnumerable<Value> data) : ItemProvider<int>(ValueType.Array)
{
public Value[] Data = [..data];
protected override void InnerSet(int index, Value value)
{
Data[index] = value;
}
protected override Value InnerGet(int index)
{
return Data[index];
}
protected override void AssertValidAccesor(int index)
{
if (index < 0 || index > Data.Length)
throw new ItemProviderException($"Can not index '{index}' of array, as the index is outside its boundaries.", this);
}
protected override int ExtractIndex(Value value)
{
int index;
if (value.IsSigned)
index = (int)value.Signed;
else if (value.IsUnsigned)
index = (int)value.Unsigned;
else
throw new ItemProviderException($"Can not use {value} as an accessor, as it is not an integer.", this);
return index;
}
public override string ToString() => ToString(true);
public string ToString(bool detail) => detail ? $"[{string.Join(", ", Data)}]" : $"Array[{Data.Length}]";
}

View File

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

View File

@ -1,23 +0,0 @@
using System;
namespace Qrakhen.Qamp.Core.Values.Objects;
public class Timestamp(Value timeStamp) : Native(nameof(Timestamp))
{
public static readonly string DefaultFormat = "H:m:s d.M.y";
// cheap ass workaround i know
private DateTime _dateTime = new DateTime(timeStamp.Signed * 10);
public Value Raw = timeStamp;
public Value Format(Value? format = null)
{
return String.Make(_dateTime.ToString(format?.Ptr.As<String>()?.Value ?? DefaultFormat));
}
public override string ToString() => ToString(true);
public string ToString(bool detail) => detail ?
$"[Timestamp <{_dateTime}>]" :
_dateTime.ToString(DefaultFormat);
}

View File

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

View File

@ -1,11 +0,0 @@
namespace Qrakhen.Qamp.Core.Values.Objects;
public class Extension(ValueType targetType, Function function) : Context(function)
{
public ValueType TargetType = targetType;
public override string ToString()
{
return $"{TargetType}.{Function.Name}() (Extension)";
}
}

View File

@ -1,46 +0,0 @@
using Qrakhen.Qamp.Core.Execution;
using System.Text;
namespace Qrakhen.Qamp.Core.Values.Objects;
public class Function(string name, Segment segment, int outerCount, int argumentCount) : Obj(ValueType.Function)
{
public readonly string Name = name;
public Segment Segment = segment;
public readonly int OuterCount = outerCount;
public readonly int ArgumentCount = argumentCount;
public override string ToString() => $"{Name}()";
public byte[] Serialize(bool topLevel = false)
{
List<byte> result =
[
.. string.IsNullOrEmpty(Name) ? [] : Encoding.ASCII.GetBytes(Name),
0,
(byte)ArgumentCount,
.. OuterCount.GetBytes(),
.. Segment.Serialize()
];
if (topLevel) {
result.AddRange([0xFF, 0xF0]);
result.AddRange(String.SerializeStrings());
result.AddRange([0x0F, 0xFF, 0xFF, 0xF1]);
result.AddRange(Ptr.SerializeFunctions());
result.AddRange([0x0F, 0xFF]);
}
return result.ToArray();
}
}
// local c# function, so compiled inside the binary already
// cheap shortcut for global methods for now.
public class NativeFunction(string name, Func<Value[], Value> target, params string[] arguments) : Obj(ValueType.Native)
{
public string Name { get; } = name;
public Func<Value[], Value> Target { get; } = target;
public string[] Arguments { get; } = arguments;
public override string ToString() => $"[NativeFunction <{Name}> ({string.Join(", ", Arguments)})]";
}

View File

@ -1,19 +0,0 @@
namespace Qrakhen.Qamp.Core.Values.Objects;
public class Instance(Class @class) : Obj(ValueType.Instance)
{
public readonly Class Class = @class;
public Table Values = new();
public override string ToString()
{
string str = $"{Class.Name} {{\n";
foreach (var member in Class.Members) {
str += $" {member.Key}: {member.Value},\n";
}
foreach (var value in Values) {
str += $" {value.Key}: {value.Value},\n";
}
return $"{str}}}";
}
}

View File

@ -1,26 +0,0 @@
namespace Qrakhen.Qamp.Core.Values.Objects;
public abstract class ItemProvider<T>(ValueType type) : Obj(type)
{
public void Set(Value index, Value value)
{
T _index = ExtractIndex(index);
AssertValidAccesor(_index);
InnerSet(_index, value);
}
public Value Get(Value index)
{
T _index = ExtractIndex(index);
AssertValidAccesor(_index);
return InnerGet(_index);
}
protected abstract Value InnerGet(T index);
protected abstract void InnerSet(T index, Value value);
protected abstract void AssertValidAccesor(T index);
protected abstract T ExtractIndex(Value value);
}
public class ItemProviderException(string message, object context) : QampException(message, context);

View File

@ -1,50 +0,0 @@
namespace Qrakhen.Qamp.Core.Values.Objects;
public class List(IEnumerable<Value> data) : ItemProvider<int>(ValueType.List)
{
public List<Value> Data = [..data];
public void Add(Value value)
{
Data.Add(value);
}
public void Remove(Value index)
{
int _index = ExtractIndex(index);
AssertValidAccesor(_index);
Data.RemoveAt(_index);
}
protected override void InnerSet(int index, Value value)
{
Data[index] = value;
}
protected override Value InnerGet(int index)
{
return Data[index];
}
protected override void AssertValidAccesor(int index)
{
if (index < 0 || index > Data.Count)
throw new ItemProviderException($"Can not index '{index}' of list, as the index is outside its boundaries.", this);
}
protected override int ExtractIndex(Value value)
{
int index;
if (value.IsSigned)
index = (int)value.Signed;
else if (value.IsUnsigned)
index = (int)value.Unsigned;
else
throw new ItemProviderException($"Can not use {value} as an accessor, as it is not an integer.", this);
return index;
}
public override string ToString() => ToString(true);
public string ToString(bool detail) => detail ? $":[{string.Join(", ", Data)}]" : $"List[{Data.Count}]";
}

View File

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

View File

@ -1,9 +0,0 @@
namespace Qrakhen.Qamp.Core.Values.Objects;
public class Native(string name) : Class(name)
{
public override string ToString()
{
return $"{Name} (Native)";
}
}

View File

@ -1,42 +0,0 @@
using Qrakhen.Qamp.Core.Collections;
namespace Qrakhen.Qamp.Core.Values.Objects;
public class ObjTable(int size = 4096)
{
public static readonly ObjTable Global = new ObjTable(4096);
private readonly Table<Obj> _table = new Table<Obj>(size);
public IEnumerable<KeyValuePair<Address, Obj>> Entries => _table;
public Obj? Get(Address address) => _table[address];
public T? Get<T>(Address address) where T : Obj => _table[address] as T;
public bool TryGet(Address address, out Obj? obj) => _table.TryGet(address, out obj);
public void Free(Address address) => _table.Free(address);
public Address Register(Obj obj) => _table.Add(obj);
}
public class Obj : ITypedValue
{
internal bool __GC_Marked = false;
internal int __GC_Count = 1;
internal readonly Address __Address; // unsure lol
public readonly ValueType Type;
public ValueType ValueType => Type;
public Obj(ValueType type)
{
Type = type;
__Address = ObjTable.Global.Register(this);
}
public static Value Create(Obj obj)
{
return new Value(Ptr.Create(obj));
}
public override string ToString() => "Obj<undefined>";
}

View File

@ -1,39 +0,0 @@
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

@ -1,91 +0,0 @@
using Qrakhen.Qamp.Core.Collections;
using System.Text;
namespace Qrakhen.Qamp.Core.Values.Objects;
public class String(string? value) : Obj(ValueType.String)
{
// yea that's quite temporary
private static readonly Register<uint, String> _strings = new();
public string? Value = value;
public override string ToString() => Value ?? "null";
public Value SubString(Value start, Value length)
{
if (Value == null)
throw new RuntimeException($"Can not call substring on an empty string.");
int index = start.IsInteger ?
(int)start.Signed :
throw new RuntimeException($"{nameof(String)}.{nameof(SubString)} requires first parameter 'start' to be an integer.");
if (index < 0 || index >= Value.Length)
throw new RuntimeException($"Parameter 'start' {start} was out of bounds.");
if (length.IsVoid)
return String.Make(Value.Substring(index));
int _length = length.IsInteger ?
(int)length.Signed :
throw new RuntimeException($"{nameof(String)}.{nameof(SubString)} requires second parameter 'length' to be an integer.");
return String.Make(Value.Substring(index, _length));
}
public Value IndexOf(Value needle)
{
if (!needle.IsString)
throw new RuntimeException($"Parameter 'needle' is expected to be of type string, got {needle} instead.");
return new Value(Value?.IndexOf(needle.Ptr.As<String>()!.Value!) ?? -1L);
}
public Value Split(Value delimiter)
{
return Values.Value.Void;
}
public Value Length()
{
return new Value(Value?.Length ?? 0L);
}
public uint GetHash()
{
if (string.IsNullOrEmpty(Value))
return 0;
return Value?.GetHash() ?? 0;
}
public static Value Make(string? value)
{
String? str;
var hash = value?.GetHash() ?? 0;
if (!_strings.TryGet(hash, out str)) {
str = new String(value);
_strings.Add(hash, str);
}
return Create(str);
}
public static byte[] SerializeStrings()
{
List<byte> result = new();
foreach (var pair in _strings) {
String str = pair.Value;
if (str == null || str.__GC_Marked)
continue;
byte[] bytes;
bytes = Encoding.ASCII.GetBytes(str.Value!);
result.AddRange(pair.Key.GetBytes());
result.AddRange(bytes.Length.GetBytes());
result.AddRange(bytes);
result.Add(0);
}
return result.ToArray();
}
}

View File

@ -1,37 +0,0 @@
namespace Qrakhen.Qamp.Core.Values.Objects;
public class Structure(IEnumerable<KeyValuePair<string, Value>> data) : ItemProvider<string>(ValueType.Structure)
{
public bool Sealed;
public Dictionary<string, Value> Data = new(data);
protected override void InnerSet(string index, Value value)
{
Data[index] = value;
}
protected override Value InnerGet(string index)
{
return Data[index];
}
protected override void AssertValidAccesor(string index)
{
if (Sealed && !Data.ContainsKey(index))
throw new ItemProviderException($"Can not set non-existent key '{index}' of sealed structure.", this);
}
protected override string ExtractIndex(Value value)
{
string index;
if (value.IsString)
index = value.Ptr.As<String>()!.Value!;
else
throw new ItemProviderException($"Can not use {value} as an accessor, as it is not a string.", this);
return index;
}
public override string ToString() => ToString(true);
public string ToString(bool detail) => detail ? $"{{{string.Join("\n,", Data)}}}" : $"Structure{{{Data.Count}}}";
}

View File

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

View File

@ -1,6 +0,0 @@
namespace Qrakhen.Qamp.Core.Values.Objects;
public class TypeWrapper() : Obj(ValueType.Undefined)
{
}

View File

@ -1,82 +0,0 @@
using Qrakhen.Qamp.Core.Values.Objects;
using System.Runtime.InteropServices;
using Qrakhen.Qamp.Core.Collections;
namespace Qrakhen.Qamp.Core.Values;
// generally interesting: GCHandle.ToIntPtr(GCHandle.Alloc(obj, GCHandleType.Normal));
[StructLayout(LayoutKind.Sequential, Size = sizeof(ulong))]
public readonly struct Ptr
{
[Serialized] public readonly Address Address;
public Obj? Value {
get {
if (ObjTable.Global.TryGet(Address, out Obj? obj))
return obj;
return null;
}
}
private Ptr(Address address)
{
Address = address;
}
public static Ptr Create(Obj obj)
{
return new Ptr(obj.__Address);
}
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 Obj? As(Type type, bool throwWhenNull = true)
{
Obj? value = Convert.ChangeType(Value, type) as Obj;
if (value == null && throwWhenNull)
throw new RuntimeException($"Could not convert Object {Value} to target type {type}.", Value);
return value;
}
public override string ToString() => $"0x{Value}";
public static async Task __GC_Prepare()
{
await Task.Run(() => {
foreach (var obj in ObjTable.Global.Entries) {
obj.Value.__GC_Count = 0;
}
});
}
public static byte[] SerializeFunctions()
{
List<byte> result = new();
foreach (var element in ObjTable.Global.Entries) {
Obj obj = element.Value;
if (obj == null || obj.__GC_Marked)
continue;
byte[] bytes;
if (obj is Function function) {
bytes = function.Serialize();
} else if (obj is Context context) {
bytes = context.Function.Serialize();
} else {
continue;
}
result.AddRange(((long)element.Key).GetBytes());
result.AddRange(bytes.Length.GetBytes());
result.AddRange(bytes);
result.Add(0);
}
return result.ToArray();
}
}

View File

@ -1,11 +0,0 @@
using System.Runtime.InteropServices;
namespace Qrakhen.Qamp.Core.Values;
[StructLayout(LayoutKind.Sequential, Size = sizeof(ulong))]
public readonly unsafe struct Ref(Value* value)
{
[Serialized] public readonly Value* Value = value;
public override string ToString() => $"0x{*Value}";
}

View File

@ -1,31 +0,0 @@
using Qrakhen.Qamp.Core.Values.Objects;
namespace Qrakhen.Qamp.Core.Values;
public readonly record struct TypeInfo(ValueType Type, Type? ObjectType = null, string? TypeName = null, bool Nullable = false)
{
public static readonly TypeInfo Any = new(ValueType.Undefined, null, null, true);
public static readonly TypeInfo Void = new(ValueType.Void, null, null, false);
public bool Match(TypeInfo other)
{
if (Type != ValueType.Undefined && other.Type != ValueType.Undefined && Type != other.Type)
return false;
if (ObjectType != null && other.ObjectType != null && ObjectType != other.ObjectType)
return false;
if (TypeName != null && other.TypeName != null && TypeName != other.TypeName)
return false;
return true;
}
public static TypeInfo FromValue(Value value)
{
return new TypeInfo(
value.Type,
value.IsExt ? value.Ptr.Value?.GetType() : null,
value.IsInstance ? value.Ptr.As<Instance>()?.Class.Name : null);
}
}

View File

@ -1,219 +0,0 @@
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using System.Security.AccessControl;
using Qrakhen.Qamp.Core.Abstractions;
using Qrakhen.Qamp.Core.Values.Objects;
using String = Qrakhen.Qamp.Core.Values.Objects.String;
using T = Qrakhen.Qamp.Core.Values.ValueType;
namespace Qrakhen.Qamp.Core.Values;
// HEY. IDEA.
// have custom structs for types. this way we can handle many more thing and also more clean no????
public interface IPrimitive
{
public static abstract string Name { get; }
public static abstract int SizeOf { get; }
public ulong Raw { get; }
public string Print();
}
public readonly record struct Signed(long Value)// : IPrimitive
{
}
public interface IValue<T>
{
T Data { get; }
}
public interface ITypedValue
{
T ValueType { get; }
}
public interface IDynamicValue : IValue<ulong>
{
}
[StructLayout(LayoutKind.Explicit, Size = 0x10)]
public readonly struct Value :
IDynamicValue,
ISerialize<Value>,
IDebug<string>
{
public static readonly Value Void = new Value();
public static readonly Value True = new Value(true);
public static readonly Value False = new Value(false);
[FieldOffset(0x00)] [Serialized] public readonly ulong Unsigned;
[FieldOffset(0x00)] [Serialized] public readonly long Signed;
[FieldOffset(0x00)] [Serialized] public readonly bool Bool;
[FieldOffset(0x00)] [Serialized] public readonly char Char;
[FieldOffset(0x00)] [Serialized] public readonly double Decimal;
[FieldOffset(0x00)] [Serialized] public readonly Address Address;
[FieldOffset(0x00)] [Serialized] public readonly Ptr Ptr;
[FieldOffset(0x00)] [Serialized] public readonly Ref Ref;
public ulong Data => Unsigned;
[FieldOffset(0x08)] [Serialized] public readonly T Type;
[FieldOffset(0x0c)] [Serialized] public readonly uint Meta; // like lengths of arrays or lists
private Value(T type) => Type = type;
public Value() : this(T.Void) { }
public Value(ulong unsigned) : this(T.Unsigned) => Unsigned = unsigned;
public Value(long signed) : this(T.Signed) => Signed = signed;
public Value(char character) : this(T.Char) => Char = character;
public Value(bool boolean) : this(T.Bool) => Bool = boolean;
public Value(double @decimal) : this(T.Decimal) => Decimal = @decimal;
public Value(Address address) : this(T.Address) => Address = address;
public Value(Ptr ptr) : this(ptr.Value?.Type ?? T.Pointer) => Ptr = ptr;
public Value(Ref @ref) : this(T.Reference) => Ref = @ref;
public Value(Obj obj) : this(Ptr.Create(obj)) { } // todo: very spicy, let's see if this doesn't fuck up the GC all too bad.
public dynamic? Dynamic => AsDynamic();
public T ValueType => Type;
public TypeInfo TypeInfo => TypeInfo.FromValue(this);
public bool IsVoid => Type == T.Void;
public bool IsNumber => Type <= T.Decimal;
public bool IsSigned => Is(T.Signed);
public bool IsUnsigned => Is(T.Unsigned);
public bool IsDecimal => Is(T.Decimal);
public bool IsInteger => IsSigned || IsUnsigned;
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 IsExt => Is(T.Class) || Is(T.Instance);
public bool IsInstance => Is(T.Instance);
public bool IsFalsy => IsBool ? Bool == false : Unsigned == 0;
// another shortcut that will cost me greatly
// edit: yes, did cost me greatly, lol. pointer was pointing to random strings in string table
// ending up with dozens of strings spat out using write(), accidentally, by giving write
// an integer (table index offset) rather than a string literal. oops.
public String? AsString() => IsString ? Ptr.As<String>() : null;
public double GetDecimal() => IsDecimal ? Decimal : (double)Signed;
public string? GetString() => AsString()?.Value;
public bool Is(T type, bool exact = true)
{
return exact ? (Type & type) == type : (Type & type) > 0;
}
private unsafe dynamic? AsDynamic(bool throwWhenNull = true)
{
if (IsString)
return Ptr.As<String>()!.Value;
if (IsObj)
return Ptr.Value;
return Type switch {
T.Void => throwWhenNull ? throw new NullReferenceException() : null,
T.Unsigned => Unsigned,
T.Signed => Signed,
T.Decimal => Decimal,
T.Bool => Bool,
T.Address => Address,
T.Reference => (Value)(*Ref.Value),
_ => throwWhenNull ? throw new NullReferenceException() : null,
};
}
public static Value FromAny(object? any = null)
{
if (any == null)
return new Value();
Type type = any.GetType();
return new Value();
}
public void __gcMark()
{
if (IsObj && Ptr.Value != null) { }
}
public override bool Equals([NotNullWhen(true)] object? obj)
{
return obj switch {
Value value when Type == value.Type => Unsigned == value.Unsigned,
Value value => Dynamic?.Equals(value.Dynamic),
_ => false
};
}
public override int GetHashCode() => Unsigned.GetHashCode();
public override string ToString() => ToString(false);
public string ToString(bool includeType)
{
if (Type == T.Void)
return $"void";
if (!includeType)
return $"{AsDynamic(false)}";
string type = Type.ToString();
if (Type.HasFlag(T.Pointer))
type = $"{Ptr.Value?.Type.ToString()}@{Ptr.Address}" ?? "ptr:null";
return $"<{type}, {AsDynamic(false) ?? "null"}>";
}
public string Debug(DebugLevel level = DebugLevel.None)
{
if (level < DebugLevel.Strong)
return Debugger.GetContextString(this);
string str = $"{Debugger.GetContextString(this)}\n";
string[] lines =
[
$"{nameof(Meta)}: {Meta}",
$"{nameof(Unsigned)}: {Unsigned}",
$"{nameof(Signed)}: {Signed}",
$"{nameof(Bool)}: {Bool}",
$"{nameof(Char)}: {Char}",
$"{nameof(Decimal)}: {Decimal}",
$"{nameof(Address)}: {Address}",
$"{nameof(Ref)}: {Ref}",
$"{nameof(Ptr)}: {Ptr}"
];
return str + string.Join("\n - ", lines);
}
public byte[] Serialize()
{
List<byte> result = [.. ((ushort)Type).GetBytes() ];
byte[] bytes = Signed.GetBytes();
result.Add((byte)bytes.Length);
result.AddRange(bytes);
result.Add(0x7F);
return result.ToArray();
}
public static Value Deserialize(byte[] data)
{
return Void;
}
}
public class SerializedAttribute : Attribute;

View File

@ -1,50 +0,0 @@
using Qrakhen.Qamp.Core.Execution;
using Qrakhen.Qamp.Core.Values.Objects;
namespace Qrakhen.Qamp.Core.Values;
internal static class ValueExtensions
{
public static Value ToValue(this object obj)
{
if (obj is sbyte _byte)
return new Value((long)_byte);
if (obj is short _short)
return new Value((long)_short);
if (obj is int _int)
return new Value((long)_int);
if (obj is long _long)
return new Value(_long);
if (obj is byte _sbyte)
return new Value((ulong)_sbyte);
if (obj is ushort _ushort)
return new Value((ulong)_ushort);
if (obj is uint _uint)
return new Value((ulong)_uint);
if (obj is ulong _ulong)
return new Value((ulong)_ulong);
if (obj is bool _bool)
return new Value(_bool);
if (obj is char _char)
return new Value(_char);
if (obj is string _string)
return Objects.String.Make(_string);
if (obj is float _float)
return new Value(_float);
if (obj is double _double)
return new Value(_double);
if (obj is Obj _obj)
return Obj.Create(_obj);
throw new QampException($"Could not convert native system value {obj} to qamp value.");
}
public static object ToObject(this Value value)
{
}
}

View File

@ -1,71 +0,0 @@
namespace Qrakhen.Qamp.Core.Values;
// better/smaller/faster idea:
public enum _ValueType : byte
{
F_Primitive = 0x20,
F_Object = 0x40,
F_Collection = 0x80,
Void = 0x00,
Unsigned = 1 | F_Primitive,
Signed = 2 | F_Primitive,
Decimal = 3 | F_Primitive,
Char = 4 | F_Primitive,
Bool = 5 | F_Primitive,
}
[Flags]
public enum MetaData : ushort
{
None = 0,
ReadOnly = 0x0001, // hmmmm could be put as either a byte flagset or 2 bools too. cant think of many metadata flags.
Dynamic = 0x0002,
}
/// <summary>
/// todo: make value type a byte instead to save some memory on values
/// edit: if that's even possible with c#'s memory padding
/// </summary>
[Flags]
public enum ValueType : ushort
{
Undefined = 0xFFFF,
Void = 0x0000,
Unsigned = 0x0001,
Signed = 0x0002,
Decimal = 0x0004,
Char = 0x0008,
Bool = 0x0010,
Number = Unsigned | Signed | Decimal,
Integer = Unsigned | Signed | Char,
Primitive = Integer | Decimal | Bool,
Address = 0x0020,
// classes etc. here?
Reference = 0x1000,
Pointer = 0x2000,
Object = Pointer | 0x2000,
Native = Object | 0x0001,
String = Object | 0x0002,
Function = Object | 0x0020,
Context = Object | 0x0040,
Instance = Object | 0x0080,
Class = Object | 0x0100,
Method = Class | Function,
Outer = Object | 0x0200,
Complex = Object | 0x0800, // unsure if even needed. native does exactly that already.
ItemProvider = Object | 0x4000, // accessible with [n] or :n (getters / setters)
Array = ItemProvider | 0x0001,
List = ItemProvider | 0x0002,
Structure = ItemProvider | 0x0004,
Module = 0x8000
}

View File

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

View File

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

View File

@ -1,12 +0,0 @@
<Application x:Class="Qrakhen.Qamp.Editor.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Qrakhen.Qamp.Editor">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/Converters/Converters.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>

View File

@ -1,21 +0,0 @@
using Qrakhen.Qamp.Editor.ViewModel;
using System.Configuration;
using System.Data;
using System.Windows;
namespace Qrakhen.Qamp.Editor;
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
MainWindow window = new MainWindow() {
DataContext = new MainViewModel()
};
window.Show();
}
}

View File

@ -1,10 +0,0 @@
using System.Windows;
[assembly: ThemeInfo(
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
//(used if a resource is not found in the page,
// or application resource dictionaries)
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
//(used if a resource is not found in the page,
// app, or any theme specific resource dictionaries)
)]

Some files were not shown because too many files have changed in this diff Show More