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/
|
||||
x86/
|
||||
x64/
|
||||
Release/
|
||||
Build/
|
||||
bin/
|
||||
*/bin/
|
||||
*/bin/*
|
||||
obj/
|
||||
log/
|
||||
lib/
|
||||
.vs/
|
||||
*.nocmt.*
|
||||
*.sqi
|
||||
*.tmp
|
||||
*.log
|
||||
*.depend
|
||||
*.stackdump
|
||||
*.layout
|
||||
*.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