Compare commits
No commits in common. "dev" and "main" have entirely different histories.
|
|
@ -1,5 +0,0 @@
|
||||||
root = true
|
|
||||||
|
|
||||||
[*.cs]
|
|
||||||
indent_style = space
|
|
||||||
indent_size = 3
|
|
||||||
|
|
@ -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/
|
||||||
|
|
|
||||||
21
LICENSE.txt
21
LICENSE.txt
|
|
@ -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.
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
public enum ConsoleCode
|
|
||||||
{
|
|
||||||
Error = -1,
|
|
||||||
OK = 0,
|
|
||||||
Exit = 1
|
|
||||||
}
|
|
||||||
|
|
@ -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; }
|
|
||||||
}
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
{
|
|
||||||
"profiles": {
|
|
||||||
"Qrakhen.Qamp.CLI": {
|
|
||||||
"commandName": "Project",
|
|
||||||
"nativeDebugging": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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);
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
@ -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"})";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
namespace Qrakhen.Qamp.Core.Abstractions;
|
|
||||||
|
|
||||||
public interface ISerialize<TSelf>
|
|
||||||
{
|
|
||||||
byte[] Serialize();
|
|
||||||
static abstract TSelf Deserialize(byte[] data);
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
using System.Numerics;
|
|
||||||
|
|
||||||
namespace Qrakhen.Qamp.Core.Collections.Abstractions;
|
|
||||||
|
|
||||||
public interface IAdd<out TKey, in TValue>
|
|
||||||
{
|
|
||||||
TKey Add(TValue value);
|
|
||||||
}
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
namespace Qrakhen.Qamp.Core.Collections.Abstractions;
|
|
||||||
|
|
||||||
public interface IConsumable<in T>
|
|
||||||
{
|
|
||||||
void Consume(T expected, string? message = null);
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
namespace Qrakhen.Qamp.Core.Collections.Abstractions;
|
|
||||||
|
|
||||||
public interface IGet<in TKey, out TValue>
|
|
||||||
{
|
|
||||||
TValue Get(TKey index);
|
|
||||||
}
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
namespace Qrakhen.Qamp.Core.Collections.Abstractions;
|
|
||||||
|
|
||||||
public interface IGetSet<in TKey, TValue> : IGet<TKey, TValue>, ISet<TKey, TValue>;
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
namespace Qrakhen.Qamp.Core.Collections.Abstractions;
|
|
||||||
|
|
||||||
public interface IPeekable<out T>
|
|
||||||
{
|
|
||||||
T Peek(int delta = -1);
|
|
||||||
}
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
namespace Qrakhen.Qamp.Core.Collections.Abstractions;
|
|
||||||
|
|
||||||
public interface ISeekable<TKey, out TValue>
|
|
||||||
{
|
|
||||||
TKey Position { get; }
|
|
||||||
TValue Seek(TKey position);
|
|
||||||
}
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
@ -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();
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
namespace Qrakhen.Qamp.Core.Collections.Abstractions;
|
|
||||||
|
|
||||||
public interface ISteppable<out T>
|
|
||||||
{
|
|
||||||
bool Done { get; }
|
|
||||||
T Next();
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
namespace Qrakhen.Qamp.Core.Collections.Abstractions;
|
|
||||||
|
|
||||||
public interface IToArray<out T>
|
|
||||||
{
|
|
||||||
T[] ToArray();
|
|
||||||
}
|
|
||||||
|
|
@ -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();
|
|
||||||
}
|
|
||||||
|
|
@ -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();
|
|
||||||
}
|
|
||||||
|
|
@ -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}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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();
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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();
|
|
||||||
}
|
|
||||||
|
|
@ -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();
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
namespace Qrakhen.Qamp.Core.Compilation.Builders;
|
|
||||||
|
|
||||||
public class ClassBuilder
|
|
||||||
{
|
|
||||||
public string Name;
|
|
||||||
|
|
||||||
public ClassBuilder? Outer;
|
|
||||||
public bool IsDerived;
|
|
||||||
}
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
namespace Qrakhen.Qamp.Core.Compilation.Builders;
|
|
||||||
|
|
||||||
public interface IBuilder<TOut>
|
|
||||||
{
|
|
||||||
public TOut Build();
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
@ -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);
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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}>";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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 & 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})";
|
|
||||||
}
|
|
||||||
|
|
@ -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) { }
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
@ -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}"]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
@ -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.
|
|
||||||
|
|
||||||
|
|
@ -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, "!>");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
@ -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();
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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));
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
|
|
@ -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}]";
|
|
||||||
}
|
|
||||||
|
|
@ -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)";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
@ -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}(){{}}";
|
|
||||||
}
|
|
||||||
|
|
@ -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)";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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)})]";
|
|
||||||
}
|
|
||||||
|
|
@ -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}}}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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);
|
|
||||||
|
|
@ -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}]";
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
namespace Qrakhen.Qamp.Core.Values.Objects;
|
|
||||||
|
|
||||||
public class Native(string name) : Class(name)
|
|
||||||
{
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return $"{Name} (Native)";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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>";
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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}}}";
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
using Qrakhen.Qamp.Core.Collections;
|
|
||||||
|
|
||||||
namespace Qrakhen.Qamp.Core.Values.Objects;
|
|
||||||
|
|
||||||
public class Table : Register<string, Value>
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
namespace Qrakhen.Qamp.Core.Values.Objects;
|
|
||||||
|
|
||||||
public class TypeWrapper() : Obj(ValueType.Undefined)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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}";
|
|
||||||
}
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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;
|
|
||||||
|
|
@ -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)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
namespace Qrakhen.Qamp.Digest;
|
|
||||||
|
|
||||||
public class Class1
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -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
Loading…
Reference in New Issue