initial commit
This commit is contained in:
parent
8fb165007d
commit
da653b3ce5
|
|
@ -0,0 +1,308 @@
|
||||||
|
|
||||||
|
using Qrakhen.Qamp.Core;
|
||||||
|
using Qrakhen.Qamp.Core.Execution;
|
||||||
|
using Qrakhen.Qamp.Core.Tokenization;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
char[] Ignored = [ '\0', '\b' ];
|
||||||
|
Stack<byte[]> History = [];
|
||||||
|
ConsoleKeyInfo input;
|
||||||
|
bool useSyntaxHighlighting = false;
|
||||||
|
(int x, int y) cursor = (0, 0);
|
||||||
|
ConsoleCode code = ConsoleCode.Error;
|
||||||
|
Runner runner = new Runner(new Options());
|
||||||
|
ConsoleInterface cli = new ConsoleInterface(Encoding.Default);
|
||||||
|
do {
|
||||||
|
/*Console.ForegroundColor = ConsoleColor.White;
|
||||||
|
MemoryStream stream = new MemoryStream();
|
||||||
|
Console.Write(" <: ");
|
||||||
|
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;
|
||||||
|
Console.Write($"\n #: {nameof(useSyntaxHighlighting)} = {useSyntaxHighlighting}\n <: ");
|
||||||
|
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.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 {
|
||||||
|
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);
|
||||||
|
|
||||||
|
class ConsoleInterface
|
||||||
|
{
|
||||||
|
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 ConsoleInterface(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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ConsoleCode
|
||||||
|
{
|
||||||
|
Error = -1,
|
||||||
|
OK = 0,
|
||||||
|
Exit = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
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.Red;
|
||||||
|
if (token.Type.IsNumber())
|
||||||
|
color = ConsoleColor.DarkCyan;
|
||||||
|
if (token.Type.IsOperator())
|
||||||
|
color = ConsoleColor.DarkGreen;
|
||||||
|
if (token.Type.IsBracket())
|
||||||
|
color = ConsoleColor.Yellow;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Qrakhen.Qamp.Core\Qrakhen.Qamp.Core.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,89 @@
|
||||||
|
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();
|
||||||
|
Console.WriteLine($" ::: Registered new benchmark clock '{key}'");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(message))
|
||||||
|
Console.WriteLine($" ::: {key}:{line} > {message}");
|
||||||
|
|
||||||
|
if (sw.IsRunning)
|
||||||
|
Console.WriteLine($" ::: {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();
|
||||||
|
_logger.Debug($" ::: {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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,61 @@
|
||||||
|
using System.Collections;
|
||||||
|
|
||||||
|
namespace Qrakhen.Qamp.Core.Collections;
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
using System.Collections;
|
||||||
|
|
||||||
|
namespace Qrakhen.Qamp.Core.Collections;
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
using System.Collections;
|
||||||
|
|
||||||
|
namespace Qrakhen.Qamp.Core.Collections;
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
namespace Qrakhen.Qamp.Core.Collections;
|
||||||
|
|
||||||
|
public class Register<TKey, TValue> :
|
||||||
|
IGetSet<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 TValue Get(TKey key) => _data[key];
|
||||||
|
public void Set(TKey key, TValue value) => _data[key] = value;
|
||||||
|
public bool Has(TKey key) => _data.ContainsKey(key);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
namespace Qrakhen.Qamp.Core.Collections;
|
||||||
|
|
||||||
|
public class StackLike<T> : Expander<T>,
|
||||||
|
IPush<long, T>,
|
||||||
|
IPop<T>,
|
||||||
|
ISeekable<long, T>,
|
||||||
|
IPeekable<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;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
namespace Qrakhen.Qamp.Core.Collections;
|
||||||
|
|
||||||
|
public class StackRegister<T> : Expander<T>, IToArray<T>
|
||||||
|
{
|
||||||
|
public StackRegister(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 Add(T[] array)
|
||||||
|
{
|
||||||
|
foreach (T value in array) {
|
||||||
|
Add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Add(IEnumerable<T> array)
|
||||||
|
{
|
||||||
|
foreach (T value in array) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
using Qrakhen.Qamp.Core.Collections;
|
||||||
|
|
||||||
|
namespace Qrakhen.Qamp.Core.Compilation.Builders;
|
||||||
|
|
||||||
|
public class Builder
|
||||||
|
{
|
||||||
|
public Builder? Outer;
|
||||||
|
public FunctionBuilder Function = new();
|
||||||
|
|
||||||
|
public StackLike<Local> Locals = [];
|
||||||
|
public StackLike<Outer> Outers = [];
|
||||||
|
public int ScopeDepth = 0;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
namespace Qrakhen.Qamp.Core.Compilation.Builders;
|
||||||
|
|
||||||
|
public class ClassBuilder
|
||||||
|
{
|
||||||
|
public ClassBuilder? Outer;
|
||||||
|
public bool IsDerived;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
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 Function Build()
|
||||||
|
{
|
||||||
|
return new Function(Name, Segment.Build(), OuterCount, ArgumentCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
using Qrakhen.Qamp.Core.Collections;
|
||||||
|
using Qrakhen.Qamp.Core.Execution;
|
||||||
|
using Qrakhen.Qamp.Core.Values;
|
||||||
|
|
||||||
|
namespace Qrakhen.Qamp.Core.Compilation.Builders;
|
||||||
|
|
||||||
|
public readonly struct SegmentBuilder() : IDebug<string>
|
||||||
|
{
|
||||||
|
public readonly StackRegister<Instruction> Instructions = new();
|
||||||
|
public readonly StackRegister<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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,635 @@
|
||||||
|
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;
|
||||||
|
|
||||||
|
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, Init, Method, Script }
|
||||||
|
|
||||||
|
public class Compiler
|
||||||
|
{
|
||||||
|
// could contain state like tokens, function builder, etc. and then IDigesters use that compiler state. smart, innit?
|
||||||
|
private readonly ILogger _logger = LoggerService.Get<Compiler>();
|
||||||
|
|
||||||
|
private readonly PopStack<Token> _stack;
|
||||||
|
|
||||||
|
internal Builder Builder { get; private set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DigesterProvider
|
||||||
|
{
|
||||||
|
public object ExpressionDigester; // etc.
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Digester : ISteppable<Token>
|
||||||
|
{
|
||||||
|
private readonly Compiler _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 ClassBuilder? Class { get; private set; }
|
||||||
|
public Builder Builder { 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.Script);
|
||||||
|
_reader = reader;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Function Digest()
|
||||||
|
{
|
||||||
|
_logger.Method();
|
||||||
|
TokenPosition previousPosition = _reader.CurrentPosition;
|
||||||
|
Next();
|
||||||
|
while (!Done && !Match(T.Eof)) {
|
||||||
|
Token token = Current;
|
||||||
|
_logger.Verbose($"Digesting {token}");
|
||||||
|
if (previousPosition == _reader.CurrentPosition) {
|
||||||
|
ErrorAtCurrent("Loop detected / could not digest further");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Process();
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.Debug($"Digest done, instruction set:");
|
||||||
|
_logger.Verbose(Builder.Function.Segment.Debug());
|
||||||
|
|
||||||
|
return Function.Build();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal Builder CreateBuilder(FunctionType type)
|
||||||
|
{
|
||||||
|
Builder builder = new() {
|
||||||
|
Outer = Builder
|
||||||
|
};
|
||||||
|
|
||||||
|
if (type != FunctionType.Script)
|
||||||
|
builder.Function.Name = Previous.Value!;
|
||||||
|
|
||||||
|
builder.Locals.Push(new Local(type == FunctionType.Function ? "" : "this", 0));
|
||||||
|
Builder = builder;
|
||||||
|
return Builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal Function FinishBuilder()
|
||||||
|
{
|
||||||
|
Emit(Op.Return);
|
||||||
|
Function function = Builder.Function.Build();
|
||||||
|
Builder = Builder.Outer!;
|
||||||
|
return function;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void Process()
|
||||||
|
{
|
||||||
|
_logger.Method();
|
||||||
|
if (Match(T.Class)) DeclareClass();
|
||||||
|
else if (Match(T.Function)) DeclareFunction();
|
||||||
|
else if (Match(T.Var)) DeclareVariable();
|
||||||
|
else Statement();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void Statement()
|
||||||
|
{
|
||||||
|
_logger.Method();
|
||||||
|
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.Export)) Export();
|
||||||
|
else if (Match(T.Import)) Import();
|
||||||
|
else if (Match(T.ContextOpen)) CreateContext();
|
||||||
|
else StatementExpression();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void StatementExpression()
|
||||||
|
{
|
||||||
|
Expression();
|
||||||
|
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 Expression() => WeightedDigest(Weight.Assign);
|
||||||
|
|
||||||
|
internal void CreateContext()
|
||||||
|
{
|
||||||
|
_logger.Method();
|
||||||
|
BeginScope();
|
||||||
|
Block();
|
||||||
|
EndScope();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void Block()
|
||||||
|
{
|
||||||
|
_logger.Method();
|
||||||
|
while (!Check(T.ContextClose) && !Check(T.Eof))
|
||||||
|
Process();
|
||||||
|
Consume(T.ContextClose, "Expected '}' after context");
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void WeightedDigest(Weight precedence)
|
||||||
|
{
|
||||||
|
_logger.Method();
|
||||||
|
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()
|
||||||
|
{
|
||||||
|
_logger.Method();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void DeclareFunction()
|
||||||
|
{
|
||||||
|
long global = ParseVariable();
|
||||||
|
InitializeLocal();
|
||||||
|
CreateFunction(FunctionType.Function);
|
||||||
|
DefineVariable(global);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void DeclareVariable()
|
||||||
|
{
|
||||||
|
_logger.Method();
|
||||||
|
long global = ParseVariable();
|
||||||
|
if (Match(T.Equal))
|
||||||
|
Expression();
|
||||||
|
else
|
||||||
|
Emit(Op.Null);
|
||||||
|
Consume(T.Semicolon, "missing ; after variable declaration");
|
||||||
|
DefineVariable(global);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void Export()
|
||||||
|
{
|
||||||
|
_logger.Method();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void Import()
|
||||||
|
{
|
||||||
|
_logger.Method();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void If()
|
||||||
|
{
|
||||||
|
_logger.Method();
|
||||||
|
Consume(T.GroupOpen, "Expected '(' after if");
|
||||||
|
Expression();
|
||||||
|
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()
|
||||||
|
{
|
||||||
|
_logger.Method();
|
||||||
|
ErrorAtPrevious("Unexpected solitary 'else'");
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void While()
|
||||||
|
{
|
||||||
|
_logger.Method();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void Do()
|
||||||
|
{
|
||||||
|
_logger.Method();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void For()
|
||||||
|
{
|
||||||
|
_logger.Method();
|
||||||
|
BeginScope();
|
||||||
|
Consume(T.GroupOpen, "Expected '(' after for statement");
|
||||||
|
if (Match(T.Semicolon)) {
|
||||||
|
// nothing
|
||||||
|
} else if (Match(T.Var)) {
|
||||||
|
DeclareVariable();
|
||||||
|
} else {
|
||||||
|
StatementExpression();
|
||||||
|
}
|
||||||
|
|
||||||
|
long start = Function.Segment.Instructions.Count;
|
||||||
|
long exit = -1;
|
||||||
|
if (!Match(T.Semicolon)) {
|
||||||
|
Expression();
|
||||||
|
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;
|
||||||
|
Expression();
|
||||||
|
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()
|
||||||
|
{
|
||||||
|
_logger.Method();
|
||||||
|
Emit(Op.Return);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void Print()
|
||||||
|
{
|
||||||
|
_logger.Method();
|
||||||
|
Expression();
|
||||||
|
Consume(T.Semicolon, "Expected ';' after print call.");
|
||||||
|
Emit(Op.Print);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void TypeOf()
|
||||||
|
{
|
||||||
|
_logger.Method();
|
||||||
|
Expression();
|
||||||
|
Emit(Op.Typeof);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void BeginScope()
|
||||||
|
{
|
||||||
|
_logger.Method();
|
||||||
|
Builder.ScopeDepth++;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void EndScope()
|
||||||
|
{
|
||||||
|
_logger.Method();
|
||||||
|
Builder.ScopeDepth--;
|
||||||
|
while (Builder.Locals.Count > 0 &&
|
||||||
|
Builder.Locals.Peek().Depth > Builder.ScopeDepth) {
|
||||||
|
if (Builder.Locals.Peek().IsCaptured) {
|
||||||
|
Emit(OpCode.CloseUpvalue);
|
||||||
|
} else {
|
||||||
|
Emit(OpCode.Pop);
|
||||||
|
}
|
||||||
|
Builder.Locals.Pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void CreateFunction(FunctionType type)
|
||||||
|
{
|
||||||
|
_logger.Method();
|
||||||
|
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.Closure);
|
||||||
|
Emit(MakeConstant(Obj.Create(function)));
|
||||||
|
for (int i = 0; i < function.OuterCount; i++) {
|
||||||
|
Outer outer = next.Outers.Get(i);
|
||||||
|
Emit(outer.IsLocal ? 1 : 0);
|
||||||
|
Emit(outer.Index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
_logger.Method(name);
|
||||||
|
Builder.Locals.Push(new Local(ThrowIfEmpty(name), -1));
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void DeclareLocal(string? name)
|
||||||
|
{
|
||||||
|
if (Builder.ScopeDepth == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_logger.Method(name);
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
return MakeConstant(String.Make(name ??
|
||||||
|
throw new TokenException("Empty string value for identifier detected", _reader, Current)));
|
||||||
|
}
|
||||||
|
|
||||||
|
internal long MakeConstant(Value value)
|
||||||
|
{
|
||||||
|
_logger.Method(value.ToString());
|
||||||
|
long constant = Function.Segment.Constants.Add(value);
|
||||||
|
_logger.Verbose($"Registered constant {value} at index {constant}");
|
||||||
|
return constant;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal long ParseVariable()
|
||||||
|
{
|
||||||
|
_logger.Method();
|
||||||
|
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;
|
||||||
|
|
||||||
|
_logger.Method();
|
||||||
|
var local = Builder.Locals.Peek();
|
||||||
|
local.Depth = Builder.ScopeDepth;
|
||||||
|
Builder.Locals.Set(Builder.Locals.Position - 1, local);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void DefineVariable(long index)
|
||||||
|
{
|
||||||
|
_logger.Method(index.ToString());
|
||||||
|
if (Builder.ScopeDepth > 0) {
|
||||||
|
InitializeLocal();
|
||||||
|
} else {
|
||||||
|
Emit(Op.DefineGlobal);
|
||||||
|
Emit(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal byte ArgumentList()
|
||||||
|
{
|
||||||
|
byte count = 0;
|
||||||
|
if (!Check(T.GroupClose)) {
|
||||||
|
do {
|
||||||
|
Expression();
|
||||||
|
if (count++ >= 0xFF)
|
||||||
|
ErrorAtCurrent("How many arguments do you need?");
|
||||||
|
} while (Match(T.Comma));
|
||||||
|
}
|
||||||
|
|
||||||
|
Consume(T.GroupClose, "Expected ')' after argument list");
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void EmitConstant(Value constant)
|
||||||
|
{
|
||||||
|
Emit(Op.Constant);
|
||||||
|
Emit(MakeConstant(constant));
|
||||||
|
}
|
||||||
|
|
||||||
|
internal long EmitJump(Instruction instruction)
|
||||||
|
{
|
||||||
|
Emit(instruction);
|
||||||
|
Emit(0L);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void EmitDynamic(byte[] data)
|
||||||
|
{
|
||||||
|
Emit(data.Length | 0x80);
|
||||||
|
Emit(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void Emit(params byte[] data)
|
||||||
|
{
|
||||||
|
if (data.Length == 1)
|
||||||
|
_logger.Verbose($"Emitting {new Instruction(data[0])}");
|
||||||
|
else if (data.Length > 1)
|
||||||
|
_logger.Verbose($"Emitting {string.Join(' ', data)}");
|
||||||
|
foreach (var value in data)
|
||||||
|
Function.Segment.Instructions.Add(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 Patch(byte[] bytes, long position)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < bytes.Length; i++) {
|
||||||
|
Function.Segment.Instructions[position + i] = bytes[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
if (!Check(type))
|
||||||
|
return false;
|
||||||
|
Next();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal bool Check(T type)
|
||||||
|
=> Current.Type == type;
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
_logger.Method();
|
||||||
|
ErrorPosition = token.Position;
|
||||||
|
_logger.Error($"At token index {token.Position}: {errorMessage}");
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Qrakhen.Qamp.Core.Compilation.Digesters;
|
||||||
|
|
||||||
|
public interface IDigester
|
||||||
|
{
|
||||||
|
void Digest(Compiler compiler);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,283 @@
|
||||||
|
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.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;
|
||||||
|
case TokenType.Increment: digester.Emit(OpCode.Increment); break;
|
||||||
|
case TokenType.Decrement: digester.Emit(OpCode.Decrement); break;
|
||||||
|
default: return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Call(Digester digester, bool canAssign)
|
||||||
|
{
|
||||||
|
byte count = digester.ArgumentList();
|
||||||
|
digester.Emit(OpCode.Call, 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.Expression();
|
||||||
|
digester.Emit(OpCode.SetProperty, name.GetBytes());
|
||||||
|
} else if (digester.Match(TokenType.GroupOpen)) {
|
||||||
|
Byte argCount = 0; //argumentList();
|
||||||
|
digester.Emit(OpCode.Invoke, name.GetBytes());
|
||||||
|
digester.Emit(argCount);
|
||||||
|
} else {
|
||||||
|
digester.Emit(OpCode.GetProperty, name.GetBytes());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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.Expression();
|
||||||
|
digester.Consume(TokenType.GroupClose, "Expected ')' after expression.");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Array(Digester digester, bool canAssign)
|
||||||
|
{
|
||||||
|
int length = 0;
|
||||||
|
while (!digester.Check(TokenType.ArrayClose)) {
|
||||||
|
digester.Expression();
|
||||||
|
length++;
|
||||||
|
if (digester.Check(TokenType.Comma))
|
||||||
|
digester.Next();
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
digester.Consume(TokenType.ArrayClose, "Expected ']' after array definition");
|
||||||
|
digester.Emit(
|
||||||
|
OpCode.Array,
|
||||||
|
digester.MakeConstant(new Value((long)length)).GetBytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Add(Digester digester, bool canAssign)
|
||||||
|
{
|
||||||
|
digester.Expression();
|
||||||
|
digester.Emit(OpCode.ArrayAdd);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Index(Digester digester, bool canAssign)
|
||||||
|
{
|
||||||
|
digester.Expression();
|
||||||
|
digester.Consume(TokenType.ArrayClose, "expected ] after array accessor");
|
||||||
|
if (canAssign && digester.Match(TokenType.ArrayAdd)) {
|
||||||
|
digester.Expression();
|
||||||
|
digester.Emit(OpCode.ArrayAdd);
|
||||||
|
} else if (canAssign && digester.Match(TokenType.Equal)) {
|
||||||
|
digester.Expression();
|
||||||
|
digester.Emit(OpCode.ArraySet);
|
||||||
|
} else {
|
||||||
|
digester.Emit(OpCode.ArrayGet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Variable(Digester digester, bool canAssign)
|
||||||
|
{
|
||||||
|
OpCode Get, Set;
|
||||||
|
string name = digester.Previous.Value!;
|
||||||
|
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.Equal)) {
|
||||||
|
digester.Expression();
|
||||||
|
digester.Emit(Set);
|
||||||
|
digester.Emit(variable);
|
||||||
|
} else {
|
||||||
|
digester.Emit(Get);
|
||||||
|
digester.Emit(variable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Base(Digester digester, bool canAssign)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static void This(Digester digester, bool canAssign)
|
||||||
|
{
|
||||||
|
if (digester.Class == 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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, Index, Weight.Call);
|
||||||
|
_rules[TokenType.ArrayClose] = new Rule(null, null, Weight.None);
|
||||||
|
_rules[TokenType.ArrayAdd] = new Rule(null, null, Weight.None);
|
||||||
|
_rules[TokenType.ArrayRemove] = 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(Binary, Binary, Weight.Call);
|
||||||
|
_rules[TokenType.Decrement] = new Rule(Binary, 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.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(null, null, Weight.None);
|
||||||
|
_rules[TokenType.Export] = new Rule(null, null, Weight.None);
|
||||||
|
_rules[TokenType.Import] = new Rule(null, null, Weight.None);
|
||||||
|
_rules[TokenType.Return] = new Rule(null, null, Weight.None);
|
||||||
|
_rules[TokenType.Super] = new Rule(Base, null, Weight.None);
|
||||||
|
_rules[TokenType.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
namespace Qrakhen.Qamp.Core.IO;
|
||||||
|
|
||||||
|
public static class Console
|
||||||
|
{
|
||||||
|
public static void Write(object? any)
|
||||||
|
{
|
||||||
|
string[] split = $"{any ?? "null"}".Split('\n');
|
||||||
|
for (int i = 0; i < split.Length; i++) {
|
||||||
|
System.Console.Write((i == 0 ? " :> " : " ") + $"{split[i]}\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
using Qrakhen.Qamp.Core.Tokenization;
|
||||||
|
using Qrakhen.Qamp.Core.Values;
|
||||||
|
|
||||||
|
using V = Qrakhen.Qamp.Core.Values.ValueType;
|
||||||
|
|
||||||
|
namespace Qrakhen.Qamp.Core;
|
||||||
|
|
||||||
|
public class QampException(string message, TokenPosition position = default)
|
||||||
|
: Exception(message)
|
||||||
|
{
|
||||||
|
public readonly TokenPosition Position = position;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 ExecutionException(string message, TokenPosition position = default)
|
||||||
|
: QampException(message, position);
|
||||||
|
|
||||||
|
public class DivisionByZeroException(string? message, TokenPosition position = default)
|
||||||
|
: ExecutionException(message ?? "Can not divide by zero", position);
|
||||||
|
|
||||||
|
public class ConversionException(string message, Value value, TokenPosition position = default)
|
||||||
|
: ExecutionException(message, position)
|
||||||
|
{
|
||||||
|
public readonly Value Value = value;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
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 $"<{OpCode} ({Code})>";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
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];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
namespace Qrakhen.Qamp.Core.Execution;
|
||||||
|
|
||||||
|
public enum OpCode
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
Constant,
|
||||||
|
Null,
|
||||||
|
True,
|
||||||
|
False,
|
||||||
|
Pop,
|
||||||
|
Export,
|
||||||
|
Import,
|
||||||
|
GetLocal,
|
||||||
|
SetLocal,
|
||||||
|
GetGlobal,
|
||||||
|
DefineGlobal,
|
||||||
|
SetGlobal,
|
||||||
|
GetOuter,
|
||||||
|
SetOuter,
|
||||||
|
GetProperty,
|
||||||
|
SetProperty,
|
||||||
|
Property,
|
||||||
|
GetSuper,
|
||||||
|
Equal,
|
||||||
|
Greater,
|
||||||
|
Less,
|
||||||
|
Add,
|
||||||
|
Subtract,
|
||||||
|
Multiply,
|
||||||
|
Divide,
|
||||||
|
Modulo,
|
||||||
|
BitwiseAnd,
|
||||||
|
BitwiseOr,
|
||||||
|
BitwiseXor,
|
||||||
|
BitwiseNot,
|
||||||
|
BitwiseLeft,
|
||||||
|
BitwiseRight,
|
||||||
|
Increment,
|
||||||
|
Decrement,
|
||||||
|
Not,
|
||||||
|
Negate,
|
||||||
|
Ref,
|
||||||
|
Array,
|
||||||
|
ArrayGet,
|
||||||
|
ArraySet,
|
||||||
|
ArrayAdd,
|
||||||
|
ArrayRemove,
|
||||||
|
Print,
|
||||||
|
PrintExpr,
|
||||||
|
Typeof,
|
||||||
|
Jump,
|
||||||
|
JumpIfFalse,
|
||||||
|
Loop,
|
||||||
|
Call,
|
||||||
|
Invoke,
|
||||||
|
SuperInvoke,
|
||||||
|
Closure,
|
||||||
|
CloseUpvalue,
|
||||||
|
Return,
|
||||||
|
Class,
|
||||||
|
Inherit,
|
||||||
|
Method
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,387 @@
|
||||||
|
using Qrakhen.Qamp.Core.Collections;
|
||||||
|
using Qrakhen.Qamp.Core.Compilation;
|
||||||
|
using Qrakhen.Qamp.Core.Logging;
|
||||||
|
using Qrakhen.Qamp.Core.Tokenization;
|
||||||
|
using Qrakhen.Qamp.Core.Values;
|
||||||
|
using Qrakhen.Qamp.Core.Values.Objects;
|
||||||
|
using String = Qrakhen.Qamp.Core.Values.Objects.String;
|
||||||
|
|
||||||
|
namespace Qrakhen.Qamp.Core.Execution;
|
||||||
|
|
||||||
|
using Op = OpCode;
|
||||||
|
|
||||||
|
public class GetPtr<T>
|
||||||
|
{
|
||||||
|
public IGetSet<long, T> Target;
|
||||||
|
public long Pointer;
|
||||||
|
|
||||||
|
public GetPtr(IGetSet<long, T> target, long pointer = 0)
|
||||||
|
{
|
||||||
|
Target = target;
|
||||||
|
Pointer = pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T Next() => Target.Get(Pointer++);
|
||||||
|
|
||||||
|
public void Set(long position, T value)
|
||||||
|
{
|
||||||
|
Target.Set(position, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public T Get(long position)
|
||||||
|
{
|
||||||
|
return Target.Get(position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// this is all a bit cheesy imho
|
||||||
|
public class InstructionPtr : GetPtr<Instruction>
|
||||||
|
{
|
||||||
|
public Segment Segment;
|
||||||
|
|
||||||
|
public InstructionPtr(Segment segment, int position = 0) : base(segment.Instructions, position)
|
||||||
|
{
|
||||||
|
Segment = segment;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long NextLong()
|
||||||
|
{
|
||||||
|
long value = Segment.ReadLong(Pointer);
|
||||||
|
Pointer += sizeof(long);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Value NextConstant()
|
||||||
|
{
|
||||||
|
return Segment.Constants[NextLong()];
|
||||||
|
}
|
||||||
|
|
||||||
|
public String? GetString(long offset)
|
||||||
|
{
|
||||||
|
return Segment.Constants[offset].Ptr.Value as String;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Instruction Instruction => Segment.Instructions[Pointer];
|
||||||
|
|
||||||
|
public static Instruction operator +(InstructionPtr ptr, int value) => ptr.Segment.Instructions[ptr.Pointer + value];
|
||||||
|
|
||||||
|
public static Instruction operator -(InstructionPtr ptr, int value) => ptr.Segment.Instructions[ptr.Pointer - value];
|
||||||
|
|
||||||
|
public static InstructionPtr operator ++(InstructionPtr ptr)
|
||||||
|
{
|
||||||
|
ptr.Pointer++;
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static InstructionPtr operator --(InstructionPtr ptr)
|
||||||
|
{
|
||||||
|
ptr.Pointer--;
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Call
|
||||||
|
{
|
||||||
|
public Closure Closure;
|
||||||
|
public InstructionPtr Ptr;
|
||||||
|
public GetPtr<Value> StackPtr;
|
||||||
|
|
||||||
|
public Call(Closure closure, StackLike<Value> stack, long stackOffset)
|
||||||
|
{
|
||||||
|
Closure = closure;
|
||||||
|
Ptr = new InstructionPtr(Closure.Function.Segment);
|
||||||
|
StackPtr = new GetPtr<Value>(stack, stackOffset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe class OuterWrapper
|
||||||
|
{
|
||||||
|
public Value* Target;
|
||||||
|
public Value Stored;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Runner : IDisposable
|
||||||
|
{
|
||||||
|
private readonly ILogger _logger = LoggerService.Get<Runner>();
|
||||||
|
|
||||||
|
public readonly Options Options;
|
||||||
|
|
||||||
|
public StackLike<Call> Calls;
|
||||||
|
|
||||||
|
// todo: maybe have a pointer everywhere to stacks or arrays and use those rather than built-in pointers or cursors... ?
|
||||||
|
public StackLike<Value> Stack;
|
||||||
|
public long Cursor;
|
||||||
|
|
||||||
|
public Register<string, Value> Globals = new Register<string, Value>();
|
||||||
|
public Register<string, Value> Imports = new Register<string, Value>();
|
||||||
|
public Register<string, Value> Exports = new Register<string, Value>();
|
||||||
|
|
||||||
|
public Runner(Options options)
|
||||||
|
{
|
||||||
|
Options = options;
|
||||||
|
Calls = new StackLike<Call>(options.MaxCalls);
|
||||||
|
Stack = new StackLike<Value>(options.InitialStackSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ExecutionResult Run(Stream stream)
|
||||||
|
{
|
||||||
|
_logger.Method();
|
||||||
|
using Reader reader = new Reader(stream);
|
||||||
|
Digester digester = new Digester(reader);
|
||||||
|
Function function = digester.Digest();
|
||||||
|
File.WriteAllBytes("func.sqi", function.Segment.Serialize());
|
||||||
|
Closure closure = new Closure(function);
|
||||||
|
Push(Obj.Create(closure));
|
||||||
|
Call(closure, 0);
|
||||||
|
return Interpret();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ExecutionResult Interpret()
|
||||||
|
{
|
||||||
|
_logger.Method();
|
||||||
|
Call call = Calls.Peek(-1);
|
||||||
|
do {
|
||||||
|
Op opCode = call.Ptr.Next();
|
||||||
|
_logger.Verbose($"OpCode: {opCode}");
|
||||||
|
switch (opCode) {
|
||||||
|
case Op.Constant: Push(call.Ptr.NextConstant()); break;
|
||||||
|
case Op.Pop: Pop(); break;
|
||||||
|
|
||||||
|
case Op.Not: OpNot(); break;
|
||||||
|
case Op.Add: OpAdd(); break;
|
||||||
|
case Op.Subtract: OpSubtract(); break;
|
||||||
|
case Op.Divide: OpDivide(); break;
|
||||||
|
case Op.Modulo: OpModulo(); break;
|
||||||
|
case Op.Multiply: OpMultiply(); break;
|
||||||
|
case Op.Negate: OpNegate(); break;
|
||||||
|
case Op.BitwiseOr: OpBitwiseOr(); break;
|
||||||
|
case Op.BitwiseAnd: OpBitwiseAnd(); break;
|
||||||
|
case Op.BitwiseXor: OpBitwiseXor(); break;
|
||||||
|
case Op.BitwiseNot: OpBitwiseNot(); break;
|
||||||
|
case Op.BitwiseLeft: OpBitwiseLeft(); break;
|
||||||
|
case Op.BitwiseRight: OpBitwiseRight(); break;
|
||||||
|
|
||||||
|
case Op.GetLocal: {
|
||||||
|
long slot = call.Ptr.NextLong();
|
||||||
|
_logger.Verbose($"getting slot {slot} which is {call.StackPtr.Get(slot)}");
|
||||||
|
Push(call.StackPtr.Get(slot));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case Op.SetLocal: {
|
||||||
|
long slot = call.Ptr.NextLong();
|
||||||
|
_logger.Verbose($"setting stackptr {slot} to {Peek()}");
|
||||||
|
call.StackPtr.Set(slot, Peek());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case Op.DefineGlobal: {
|
||||||
|
string? name = call.Ptr.GetString(call.Ptr.NextLong())?.Value;
|
||||||
|
if (string.IsNullOrEmpty(name))
|
||||||
|
throw new ExecutionException($"tried to define global variable with empty name");
|
||||||
|
Globals[name] = Pop();
|
||||||
|
_logger.Verbose($"defined global {name} as {Globals[name]}");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case Op.SetGlobal: {
|
||||||
|
string? name = call.Ptr.GetString(call.Ptr.NextLong())?.Value;
|
||||||
|
if (string.IsNullOrEmpty(name))
|
||||||
|
throw new ExecutionException($"tried to set global variable with empty name");
|
||||||
|
if (!Globals.Has(name))
|
||||||
|
throw new ExecutionException($"tried to set a value of non-existing global variable");
|
||||||
|
Globals[name] = Pop();
|
||||||
|
_logger.Verbose($"set global {name} = {Globals[name]}");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo: add closure
|
||||||
|
|
||||||
|
case Op.GetGlobal: {
|
||||||
|
string? name = call.Ptr.GetString(call.Ptr.NextLong())?.Value;
|
||||||
|
if (string.IsNullOrEmpty(name))
|
||||||
|
throw new ExecutionException($"tried to set global variable with empty name");
|
||||||
|
if (!Globals.Has(name))
|
||||||
|
Push(Value.Void);
|
||||||
|
else
|
||||||
|
Push(Globals[name]);
|
||||||
|
_logger.Verbose($"got global {name} ({Peek()})");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
case Op.Typeof: {
|
||||||
|
Value value = Pop();
|
||||||
|
Push(String.Make(value.Type.ToString()));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case Op.Print: {
|
||||||
|
Value value = Pop();
|
||||||
|
IO.Console.Write(value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case Op.Return: // todo: not done
|
||||||
|
Value result = Pop();
|
||||||
|
Calls.Pop();
|
||||||
|
if (Calls.Count == 0) {
|
||||||
|
Pop();
|
||||||
|
return ExecutionResult.OK;
|
||||||
|
}
|
||||||
|
Cursor = call.StackPtr.Pointer; // well find a better way...
|
||||||
|
Push(result);
|
||||||
|
call = Calls.Peek();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
} while (call.Ptr.Segment.Instructions.Length > call.Ptr.Pointer);
|
||||||
|
|
||||||
|
return ExecutionResult.OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool Call(Closure closure, int argumentCount)
|
||||||
|
{
|
||||||
|
_logger.Method();
|
||||||
|
if (argumentCount != closure.Function.ArgumentCount)
|
||||||
|
throw new Exception($"Expected {closure.Function.ArgumentCount} arguments but got {argumentCount}");
|
||||||
|
|
||||||
|
if (Calls.Count > Options.MaxCalls)
|
||||||
|
throw new StackOverflowException($"Stack overflow {Calls.Count}");
|
||||||
|
|
||||||
|
Call call = new(closure, Stack, Stack.Position - argumentCount);
|
||||||
|
Calls.Push(call);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Value Peek(int delta = -1)
|
||||||
|
{
|
||||||
|
_logger.Method();
|
||||||
|
return Stack.Peek(delta);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Value Pop()
|
||||||
|
{
|
||||||
|
_logger.Method();
|
||||||
|
return Stack.Pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Push(Value value)
|
||||||
|
{
|
||||||
|
_logger.Method($"{value}");
|
||||||
|
Stack.Push(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OpNot()
|
||||||
|
{
|
||||||
|
Value value = Pop();
|
||||||
|
Push(ValueOperation.Not(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OpAdd()
|
||||||
|
{
|
||||||
|
Value right = Pop();
|
||||||
|
Value left = Pop();
|
||||||
|
Push(ValueOperation.Add(left, right));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OpSubtract()
|
||||||
|
{
|
||||||
|
Value right = Pop();
|
||||||
|
Value left = Pop();
|
||||||
|
Push(ValueOperation.Subtract(left, right));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OpDivide()
|
||||||
|
{
|
||||||
|
Value right = Pop();
|
||||||
|
Value left = Pop();
|
||||||
|
Push(ValueOperation.Divide(left, right));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OpModulo()
|
||||||
|
{
|
||||||
|
Value right = Pop();
|
||||||
|
Value left = Pop();
|
||||||
|
Push(ValueOperation.Modulo(left, right));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OpMultiply()
|
||||||
|
{
|
||||||
|
Value right = Pop();
|
||||||
|
Value left = Pop();
|
||||||
|
Push(ValueOperation.Multiply(left, right));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OpNegate()
|
||||||
|
{
|
||||||
|
Value value = Pop();
|
||||||
|
Push(ValueOperation.Negate(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OpBitwiseOr()
|
||||||
|
{
|
||||||
|
Value right = Pop();
|
||||||
|
Value left = Pop();
|
||||||
|
Push(ValueOperation.BitwiseOr(left, right));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OpBitwiseAnd()
|
||||||
|
{
|
||||||
|
Value right = Pop();
|
||||||
|
Value left = Pop();
|
||||||
|
Push(ValueOperation.BitwiseAnd(left, right));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OpBitwiseXor()
|
||||||
|
{
|
||||||
|
Value right = Pop();
|
||||||
|
Value left = Pop();
|
||||||
|
Push(ValueOperation.BitwiseXor(left, right));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OpBitwiseLeft()
|
||||||
|
{
|
||||||
|
Value right = Pop();
|
||||||
|
Value left = Pop();
|
||||||
|
Push(ValueOperation.BitwiseLeft(left, right));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OpBitwiseRight()
|
||||||
|
{
|
||||||
|
Value right = Pop();
|
||||||
|
Value left = Pop();
|
||||||
|
Push(ValueOperation.BitwiseRight(left, right));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OpBitwiseNot()
|
||||||
|
{
|
||||||
|
Value value = Pop();
|
||||||
|
Push(ValueOperation.BitwiseInvert(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_logger.Method();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ExecutionResult
|
||||||
|
{
|
||||||
|
OK = 0x0000,
|
||||||
|
Error = 0x1000,
|
||||||
|
Compilation = Error | 0x0001,
|
||||||
|
Execution = Error | 0x0002
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly struct Options(
|
||||||
|
int maxCalls = 0x100,
|
||||||
|
int stackSize = 0x200,
|
||||||
|
int initialStackSize = 0x80)
|
||||||
|
{
|
||||||
|
public readonly int MaxCalls = maxCalls;
|
||||||
|
public readonly int StackSize = stackSize;
|
||||||
|
public readonly int InitialStackSize = initialStackSize;
|
||||||
|
|
||||||
|
public Options() : this(0x100, 0x200, 0x80) { }
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
using Qrakhen.Qamp.Core.Collections;
|
||||||
|
using Qrakhen.Qamp.Core.Values;
|
||||||
|
|
||||||
|
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 8 bytes)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="offset"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public long ReadDynamic(long offset, out byte length)
|
||||||
|
{
|
||||||
|
length = Read(offset, 1)[0];
|
||||||
|
byte[] bytes = new byte[8];
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
bytes[i] = Instructions[offset + 1 + i];
|
||||||
|
}
|
||||||
|
return bytes.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.Select(c => c.Unsigned.GetBytes()).ToArray();
|
||||||
|
byte[] bytes = new byte[sizeof(long) * 2 + opCodes.Length + constants.Length * 8];
|
||||||
|
Array.Copy(opCodes.LongLength.GetBytes(), 0, bytes, 0, sizeof(long));
|
||||||
|
Array.Copy((constants.LongLength * sizeof(long)).GetBytes(), 0, bytes, sizeof(long), sizeof(long));
|
||||||
|
int offset = sizeof(long) * 2;
|
||||||
|
for (long i = 0; i < opCodes.LongLength; i++) {
|
||||||
|
bytes[offset++] = opCodes[i];
|
||||||
|
}
|
||||||
|
for (long i = 0; i < constants.LongLength; i++) {
|
||||||
|
for (int j = 0; j < sizeof(long); j++) {
|
||||||
|
bytes[offset++] = constants[i][j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Segment Deserialize(byte[] data)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
namespace Qrakhen.Qamp.Core;
|
||||||
|
|
||||||
|
public static class Extensions
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
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[] 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);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace Qrakhen.Qamp.Core;
|
||||||
|
|
||||||
|
public interface IAdd<out TKey, in TValue>
|
||||||
|
{
|
||||||
|
TKey Add(TValue value);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
namespace Qrakhen.Qamp.Core;
|
||||||
|
|
||||||
|
public interface ICaptureBuffer<TData, THandle>
|
||||||
|
{
|
||||||
|
THandle BeginCapture();
|
||||||
|
TData[] EndCapture(THandle handle);
|
||||||
|
int Captured(THandle handle);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
namespace Qrakhen.Qamp.Core;
|
||||||
|
|
||||||
|
public interface IConsumable<in T>
|
||||||
|
{
|
||||||
|
void Consume(T expected, string? message = null);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace Qrakhen.Qamp.Core;
|
||||||
|
|
||||||
|
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"})";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
namespace Qrakhen.Qamp.Core;
|
||||||
|
|
||||||
|
public interface IGet<in TKey, out TValue>
|
||||||
|
{
|
||||||
|
TValue Get(TKey index);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
namespace Qrakhen.Qamp.Core;
|
||||||
|
|
||||||
|
public interface IGetSet<in TKey, TValue> : IGet<TKey, TValue>, ISet<TKey, TValue>;
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
namespace Qrakhen.Qamp.Core;
|
||||||
|
|
||||||
|
public interface IPeekable<out T>
|
||||||
|
{
|
||||||
|
T Peek(int delta = -1);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
namespace Qrakhen.Qamp.Core;
|
||||||
|
|
||||||
|
public interface IReadable<out T>
|
||||||
|
{
|
||||||
|
T Read(int position);
|
||||||
|
T[] Read(int position, int length);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
using Qrakhen.Qamp.Core.Tokenization;
|
||||||
|
|
||||||
|
namespace Qrakhen.Qamp.Core;
|
||||||
|
|
||||||
|
public interface IReader<out T>
|
||||||
|
{
|
||||||
|
TokenPosition CurrentPosition { get; }
|
||||||
|
bool Done { get; }
|
||||||
|
T NextToken(bool includeCompilationIrrelevant = false);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
namespace Qrakhen.Qamp.Core;
|
||||||
|
|
||||||
|
public interface ISeekable<TKey, out TValue>
|
||||||
|
{
|
||||||
|
TKey Position { get; }
|
||||||
|
TValue Seek(TKey position);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
namespace Qrakhen.Qamp.Core;
|
||||||
|
|
||||||
|
public interface ISerialize<TSelf>
|
||||||
|
{
|
||||||
|
byte[] Serialize();
|
||||||
|
static abstract TSelf Deserialize(byte[] data);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
namespace Qrakhen.Qamp.Core;
|
||||||
|
|
||||||
|
public interface ISet<in TKey, in TValue>
|
||||||
|
{
|
||||||
|
TValue this[TKey index] { set; }
|
||||||
|
|
||||||
|
void Set(TKey index, TValue value);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
namespace Qrakhen.Qamp.Core;
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
namespace Qrakhen.Qamp.Core;
|
||||||
|
|
||||||
|
public interface ISteppable<out T>
|
||||||
|
{
|
||||||
|
bool Done { get; }
|
||||||
|
T Next();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
namespace Qrakhen.Qamp.Core;
|
||||||
|
|
||||||
|
public interface IToArray<out T>
|
||||||
|
{
|
||||||
|
T[] ToArray();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,160 @@
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Xml.XPath;
|
||||||
|
|
||||||
|
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; }
|
||||||
|
public LogLevel Level { get; set; }
|
||||||
|
|
||||||
|
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++)
|
||||||
|
Console.WriteLine($"{(i == 0 ? header.First : header.Extra)}{lines[i]}");
|
||||||
|
}
|
||||||
|
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 (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}" ]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\Qrakhen.Loq\Qrakhen.Loq.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="Loq\" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
|
|
@ -0,0 +1,118 @@
|
||||||
|
namespace Qrakhen.Qamp.Core.Tokenization;
|
||||||
|
|
||||||
|
using static TokenType;
|
||||||
|
|
||||||
|
public class Dialect
|
||||||
|
{
|
||||||
|
private char[][] _sequences;
|
||||||
|
|
||||||
|
public Dialect()
|
||||||
|
{
|
||||||
|
_sequences = new char[0x80][];
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Define(TokenType type, string sequence)
|
||||||
|
=> Define(type, sequence.ToCharArray());
|
||||||
|
|
||||||
|
public void Define(TokenType type, char[] sequence)
|
||||||
|
{
|
||||||
|
_sequences[(int)type] = sequence;
|
||||||
|
}
|
||||||
|
|
||||||
|
public char[] Get(TokenType type)
|
||||||
|
=> _sequences[(int)type];
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DefaultDialect : Dialect
|
||||||
|
{
|
||||||
|
public DefaultDialect()
|
||||||
|
{
|
||||||
|
Define(Null, "null");
|
||||||
|
Define(GroupOpen, "(");
|
||||||
|
Define(GroupClose, ")");
|
||||||
|
Define(ContextOpen, "{");
|
||||||
|
Define(ContextClose, "}");
|
||||||
|
Define(ArrayOpen, "[");
|
||||||
|
Define(ArrayClose, "]");
|
||||||
|
Define(ArrayAdd, "<+");
|
||||||
|
Define(ArrayRemove, "->");
|
||||||
|
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(Super, "base");
|
||||||
|
Define(TypeOf, "typeof");
|
||||||
|
Define(Print, "print");
|
||||||
|
Define(Import, "import");
|
||||||
|
Define(Export, "export");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ClassicDialect : DefaultDialect
|
||||||
|
{
|
||||||
|
public ClassicDialect()
|
||||||
|
{
|
||||||
|
Define(Null, "_");
|
||||||
|
Define(ArrayAdd, "<+");
|
||||||
|
Define(ArrayRemove, "->");
|
||||||
|
Define(Equal, "<~");
|
||||||
|
Define(This, ".~");
|
||||||
|
Define(Var, "*~");
|
||||||
|
Define(Return, "<:");
|
||||||
|
Define(Ref, "*&");
|
||||||
|
Define(Function, "fq");
|
||||||
|
Define(Super, "^~");
|
||||||
|
Define(TypeOf, "?:");
|
||||||
|
Define(Print, "::");
|
||||||
|
Define(Import, "<!");
|
||||||
|
Define(Export, "!>");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,333 @@
|
||||||
|
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();
|
||||||
|
|
||||||
|
static ReaderPatterns()
|
||||||
|
{
|
||||||
|
Keywords["false"] = False;
|
||||||
|
Keywords["true"] = True;
|
||||||
|
Keywords["null"] = Null;
|
||||||
|
Keywords["and"] = And;
|
||||||
|
Keywords["else"] = Else;
|
||||||
|
Keywords["for"] = For;
|
||||||
|
Keywords["if"] = If;
|
||||||
|
Keywords["or"] = Or;
|
||||||
|
Keywords["this"] = This;
|
||||||
|
Keywords["var"] = Var;
|
||||||
|
Keywords["while"] = While;
|
||||||
|
Keywords["do"] = Do;
|
||||||
|
Keywords["typeof"] = TypeOf;
|
||||||
|
Keywords["ref"] = Ref;
|
||||||
|
Keywords["function"] = Function;
|
||||||
|
Keywords["return"] = Return;
|
||||||
|
Keywords["class"] = Class;
|
||||||
|
Keywords["super"] = Super;
|
||||||
|
Keywords["print"] = Print;
|
||||||
|
Keywords["import"] = Import;
|
||||||
|
Keywords["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),
|
||||||
|
'.' => MakeToken(Dot, buffer),
|
||||||
|
',' => MakeToken(Comma, buffer),
|
||||||
|
';' => MakeToken(Semicolon, buffer),
|
||||||
|
':' => MakeToken(Colon, buffer),
|
||||||
|
'&' => Check('&') ?
|
||||||
|
MakeToken(And, buffer + Next()) :
|
||||||
|
MakeToken(BitwiseAnd, buffer),
|
||||||
|
'^' => 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(StarEqual, buffer + Next()) :
|
||||||
|
MakeToken(Star, buffer),
|
||||||
|
'=' => Check('=') ?
|
||||||
|
MakeToken(EqualEqual, buffer + Next()) :
|
||||||
|
MakeToken(Equal, buffer),
|
||||||
|
'<' => Check('<') ?
|
||||||
|
MakeToken(BitwiseLeft, buffer + Next()) :
|
||||||
|
Check('=') ?
|
||||||
|
MakeToken(LessEqual, buffer + Next()) :
|
||||||
|
MakeToken(Less, buffer),
|
||||||
|
'>' => Check('>') ?
|
||||||
|
MakeToken(BitwiseRight, buffer + Next()) :
|
||||||
|
Check('=') ?
|
||||||
|
MakeToken(GreaterEqual, buffer + Next()) :
|
||||||
|
MakeToken(Greater, buffer),
|
||||||
|
'~' => MakeToken(BitwiseNot, buffer),
|
||||||
|
'?' => Check('?') ?
|
||||||
|
MakeToken(DoubleQuestion, 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,126 @@
|
||||||
|
namespace Qrakhen.Qamp.Core.Tokenization;
|
||||||
|
|
||||||
|
using static TokenType;
|
||||||
|
|
||||||
|
public enum TokenType
|
||||||
|
{
|
||||||
|
Error,
|
||||||
|
Void,
|
||||||
|
Null,
|
||||||
|
|
||||||
|
Eof,
|
||||||
|
NewLine,
|
||||||
|
Whitespace,
|
||||||
|
Comment,
|
||||||
|
|
||||||
|
Bracket = 1 << 8,
|
||||||
|
GroupOpen = Bracket | 1,
|
||||||
|
GroupClose = Bracket | 2,
|
||||||
|
|
||||||
|
ContextOpen = Bracket | 3,
|
||||||
|
ContextClose = Bracket | 4,
|
||||||
|
|
||||||
|
ArrayOpen = Bracket | 5,
|
||||||
|
ArrayClose = Bracket | 6,
|
||||||
|
|
||||||
|
ArrayAdd,
|
||||||
|
ArrayRemove,
|
||||||
|
|
||||||
|
Comma,
|
||||||
|
Dot,
|
||||||
|
Colon,
|
||||||
|
Semicolon,
|
||||||
|
|
||||||
|
Minus,
|
||||||
|
Plus,
|
||||||
|
MinusEqual,
|
||||||
|
PlusEqual,
|
||||||
|
Decrement,
|
||||||
|
Increment,
|
||||||
|
|
||||||
|
Slash,
|
||||||
|
SlashEqual,
|
||||||
|
Star,
|
||||||
|
StarEqual,
|
||||||
|
Modulo,
|
||||||
|
ModuloEqual,
|
||||||
|
|
||||||
|
BitwiseAnd,
|
||||||
|
BitwiseOr,
|
||||||
|
BitwiseXor,
|
||||||
|
BitwiseNot,
|
||||||
|
|
||||||
|
BitwiseLeft,
|
||||||
|
BitwiseRight,
|
||||||
|
|
||||||
|
Bang,
|
||||||
|
BangEqual,
|
||||||
|
Equal,
|
||||||
|
EqualEqual,
|
||||||
|
Greater,
|
||||||
|
GreaterEqual,
|
||||||
|
Less,
|
||||||
|
LessEqual,
|
||||||
|
|
||||||
|
Question,
|
||||||
|
DoubleQuestion,
|
||||||
|
|
||||||
|
Identifier,
|
||||||
|
String,
|
||||||
|
Integer,
|
||||||
|
Decimal,
|
||||||
|
Hexadecimal,
|
||||||
|
|
||||||
|
And,
|
||||||
|
Else,
|
||||||
|
False,
|
||||||
|
For,
|
||||||
|
If,
|
||||||
|
Or,
|
||||||
|
This,
|
||||||
|
True,
|
||||||
|
Var,
|
||||||
|
While,
|
||||||
|
Do,
|
||||||
|
Return,
|
||||||
|
|
||||||
|
Ref,
|
||||||
|
Function,
|
||||||
|
Class,
|
||||||
|
Super,
|
||||||
|
|
||||||
|
TypeOf,
|
||||||
|
Print,
|
||||||
|
|
||||||
|
Import,
|
||||||
|
Export
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class TokenTypeExtensions
|
||||||
|
{
|
||||||
|
public static bool IsBracket(this TokenType type)
|
||||||
|
=> type is GroupClose or GroupOpen or ContextClose or ContextOpen or ArrayClose or ArrayOpen;
|
||||||
|
|
||||||
|
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 Else or For or While or Do or Return or And or Or;
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Qrakhen.Qamp.Core.Values;
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Size = sizeof(long))]
|
||||||
|
public readonly record struct Address(long Value)
|
||||||
|
{
|
||||||
|
public override string ToString() => $"0x{Value:x8}";
|
||||||
|
|
||||||
|
public static implicit operator long(Address address) => address.Value;
|
||||||
|
public static implicit operator Address(long value) => new(value);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
namespace Qrakhen.Qamp.Core.Values.Objects;
|
||||||
|
|
||||||
|
public class Array(IEnumerable<Value> data) : Obj(ValueType.Array)
|
||||||
|
{
|
||||||
|
public List<Value> Data = [..data];
|
||||||
|
|
||||||
|
public override string ToString() => $"Array<{Data.Count}>";
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
using Qrakhen.Qamp.Core.Collections;
|
||||||
|
using Qrakhen.Qamp.Core.Execution;
|
||||||
|
|
||||||
|
namespace Qrakhen.Qamp.Core.Values.Objects;
|
||||||
|
|
||||||
|
public class Closure(Function function) : Obj(ValueType.Context)
|
||||||
|
{
|
||||||
|
public Function Function = function;
|
||||||
|
public StackRegister<OuterWrapper> PreValues = new();
|
||||||
|
|
||||||
|
public override string ToString() => "{}";
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
using Qrakhen.Qamp.Core.Execution;
|
||||||
|
|
||||||
|
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}()";
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
namespace Qrakhen.Qamp.Core.Values.Objects;
|
||||||
|
|
||||||
|
public class Obj(ValueType type) : IValue
|
||||||
|
{
|
||||||
|
public readonly ValueType Type = type;
|
||||||
|
|
||||||
|
public ValueType ValueType => Type;
|
||||||
|
|
||||||
|
public static Value Create(Obj obj)
|
||||||
|
{
|
||||||
|
return new Value(new Ptr(obj));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString() => "Obj<undefined>";
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
namespace Qrakhen.Qamp.Core.Values.Objects;
|
||||||
|
|
||||||
|
public class String(string? value) : Obj(ValueType.String)
|
||||||
|
{
|
||||||
|
public string? Value = value;
|
||||||
|
|
||||||
|
public static Value Make(string? value)
|
||||||
|
{
|
||||||
|
return Create(new String(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString() => Value ?? "null";
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
namespace Qrakhen.Qamp.Core.Values.Objects;
|
||||||
|
|
||||||
|
public class TypeWrapper() : Obj(ValueType.Undefined)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
using Qrakhen.Qamp.Core.Values.Objects;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using Qrakhen.Qamp.Core.Collections;
|
||||||
|
|
||||||
|
namespace Qrakhen.Qamp.Core.Values;
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Size = sizeof(ulong))]
|
||||||
|
public readonly struct Ptr
|
||||||
|
{
|
||||||
|
private static readonly StackRegister<Obj> _register = new(0x10);
|
||||||
|
|
||||||
|
[Serialized] public readonly Address Address;
|
||||||
|
|
||||||
|
//private readonly IntPtr _pointer;
|
||||||
|
//public IntPtr Pointer => _pointer;
|
||||||
|
|
||||||
|
public Obj? Value {
|
||||||
|
get {
|
||||||
|
if (_register.TryGet(Address, out Obj obj))
|
||||||
|
return obj;
|
||||||
|
return null;
|
||||||
|
//if (_pointer == IntPtr.Zero)
|
||||||
|
// throw new ObjectDisposedException(nameof(Ptr));
|
||||||
|
//return (Obj)GCHandle.FromIntPtr(_pointer).Target!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Ptr(Obj obj)
|
||||||
|
{
|
||||||
|
//_pointer = GCHandle.ToIntPtr(GCHandle.Alloc(obj, GCHandleType.Normal));
|
||||||
|
Address = _register.Add(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString() => $"0x{Value}";
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
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}";
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,124 @@
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
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;
|
||||||
|
|
||||||
|
public interface IValue
|
||||||
|
{
|
||||||
|
ValueType ValueType { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Explicit, Size = 0x10)]
|
||||||
|
public readonly struct Value : IValue, IDebug<string>
|
||||||
|
{
|
||||||
|
public static readonly Value Void = new Value();
|
||||||
|
|
||||||
|
[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;
|
||||||
|
|
||||||
|
[FieldOffset(0x08)] [Serialized] public readonly T Type;
|
||||||
|
|
||||||
|
[FieldOffset(0x0c)] [Serialized] public readonly uint Meta; // like lengths of arrays or lists
|
||||||
|
|
||||||
|
public T ValueType => Type;
|
||||||
|
|
||||||
|
private Value(T type) => Type = type;
|
||||||
|
|
||||||
|
public Value() : this(T.Void) { }
|
||||||
|
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 dynamic? Dynamic => AsDynamic();
|
||||||
|
|
||||||
|
public bool Is(T type, bool exact = true)
|
||||||
|
{
|
||||||
|
return exact ? (Type & type) == type : (Type & type) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe dynamic? AsDynamic(bool throwWhenNull = true)
|
||||||
|
{
|
||||||
|
if (Type.HasFlag(T.Object))
|
||||||
|
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 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 unsafe string ToString()
|
||||||
|
{
|
||||||
|
string type = Type.ToString();
|
||||||
|
if (Type.HasFlag(T.Pointer))
|
||||||
|
type = Ptr.Value?.Type.ToString() ?? "NullPtr";
|
||||||
|
|
||||||
|
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 class SerializedAttribute : Attribute;
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
using Qrakhen.Qamp.Core.Execution;
|
||||||
|
|
||||||
|
namespace Qrakhen.Qamp.Core.Values;
|
||||||
|
|
||||||
|
internal static class ValueExtensions
|
||||||
|
{
|
||||||
|
public static Value ExecuteOperation(this Value value, Value other, OpCode operation)
|
||||||
|
{
|
||||||
|
return new Value();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,121 @@
|
||||||
|
using Qrakhen.Qamp.Core.Logging;
|
||||||
|
|
||||||
|
namespace Qrakhen.Qamp.Core.Values;
|
||||||
|
|
||||||
|
public static class ValueOperation
|
||||||
|
{
|
||||||
|
private static ILogger _logger = LoggerService.Get(nameof(ValueOperation));
|
||||||
|
|
||||||
|
public static Value Not(Value v)
|
||||||
|
{
|
||||||
|
_logger.Method($"{v}");
|
||||||
|
Assert.NotVoid(v);
|
||||||
|
ulong inner = v.Unsigned;
|
||||||
|
return new Value(inner == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Value Add(Value a, Value b)
|
||||||
|
{
|
||||||
|
_logger.Method($"{a}, {b}");
|
||||||
|
Assert.NotVoid(a, b);
|
||||||
|
Assert.IsPrimitive(a, b);
|
||||||
|
bool convert = a.Type != b.Type; // meh, lets make c# do that for us for now
|
||||||
|
return new Value(a.Dynamic + b.Dynamic);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Value Subtract(Value a, Value b)
|
||||||
|
{
|
||||||
|
_logger.Method($"{a}, {b}");
|
||||||
|
Assert.NotVoid(a, b);
|
||||||
|
Assert.IsPrimitive(a, b);
|
||||||
|
return new Value(a.Dynamic - b.Dynamic);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Value Divide(Value a, Value b)
|
||||||
|
{
|
||||||
|
_logger.Method($"{a}, {b}");
|
||||||
|
Assert.NotVoid(a, b);
|
||||||
|
Assert.IsPrimitive(a, b);
|
||||||
|
Assert.NotZero(b);
|
||||||
|
return new Value(a.Dynamic / b.Dynamic);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Value Modulo(Value a, Value b)
|
||||||
|
{
|
||||||
|
_logger.Method($"{a}, {b}");
|
||||||
|
Assert.NotVoid(a, b);
|
||||||
|
Assert.IsPrimitive(a, b);
|
||||||
|
Assert.NotZero(b);
|
||||||
|
return new Value(a.Dynamic % b.Dynamic);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Value Multiply(Value a, Value b)
|
||||||
|
{
|
||||||
|
_logger.Method($"{a}, {b}");
|
||||||
|
Assert.NotVoid(a, b);
|
||||||
|
Assert.IsPrimitive(a, b);
|
||||||
|
return new Value(a.Dynamic * b.Dynamic);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Value Negate(Value a)
|
||||||
|
{
|
||||||
|
_logger.Method($"{a}");
|
||||||
|
Assert.NotVoid(a);
|
||||||
|
Assert.IsPrimitive(a);
|
||||||
|
return new Value(-a.Dynamic);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Value BitwiseOr(Value a, Value b)
|
||||||
|
{
|
||||||
|
_logger.Method($"{a}, {b}");
|
||||||
|
Assert.NotVoid(a, b);
|
||||||
|
Assert.IsPrimitive(a, b);
|
||||||
|
Assert.IsInteger(a, b);
|
||||||
|
return new Value(a.Dynamic | b.Dynamic);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Value BitwiseAnd(Value a, Value b)
|
||||||
|
{
|
||||||
|
_logger.Method($"{a}, {b}");
|
||||||
|
Assert.NotVoid(a, b);
|
||||||
|
Assert.IsPrimitive(a, b);
|
||||||
|
Assert.IsInteger(a, b);
|
||||||
|
return new Value(a.Dynamic & b.Dynamic);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Value BitwiseXor(Value a, Value b)
|
||||||
|
{
|
||||||
|
_logger.Method($"{a}, {b}");
|
||||||
|
Assert.NotVoid(a, b);
|
||||||
|
Assert.IsPrimitive(a, b);
|
||||||
|
Assert.IsInteger(a, b);
|
||||||
|
return new Value(a.Dynamic ^ b.Dynamic);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Value BitwiseLeft(Value a, Value b)
|
||||||
|
{
|
||||||
|
_logger.Method($"{a}, {b}");
|
||||||
|
Assert.NotVoid(a, b);
|
||||||
|
Assert.IsPrimitive(a, b);
|
||||||
|
Assert.IsInteger(a, b);
|
||||||
|
return new Value(a.Dynamic << (int)(b.Dynamic ?? 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Value BitwiseRight(Value a, Value b)
|
||||||
|
{
|
||||||
|
_logger.Method($"{a}, {b}");
|
||||||
|
Assert.NotVoid(a, b);
|
||||||
|
Assert.IsPrimitive(a, b);
|
||||||
|
Assert.IsInteger(a, b);
|
||||||
|
return new Value(a.Dynamic >> (int)(b.Dynamic ?? 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Value BitwiseInvert(Value a)
|
||||||
|
{
|
||||||
|
_logger.Method($"{a}");
|
||||||
|
Assert.NotVoid(a);
|
||||||
|
Assert.IsPrimitive(a);
|
||||||
|
Assert.IsInteger(a);
|
||||||
|
return new Value(~a.Dynamic);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
namespace Qrakhen.Qamp.Core.Values;
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
public enum ValueType
|
||||||
|
{
|
||||||
|
Undefined = -1,
|
||||||
|
Void = 0x0000,
|
||||||
|
Unsigned = 0x0001,
|
||||||
|
Signed = 0x0002,
|
||||||
|
Char = 0x0004,
|
||||||
|
Decimal = 0x0008,
|
||||||
|
Bool = 0x0010,
|
||||||
|
|
||||||
|
Integer = Unsigned | Signed | Char,
|
||||||
|
Primitive = Integer | Decimal | Bool,
|
||||||
|
|
||||||
|
Address = 0x0020, // coded with : symbol
|
||||||
|
|
||||||
|
// classes etc. here?
|
||||||
|
|
||||||
|
Reference = 0x1000,
|
||||||
|
Pointer = 0x2000,
|
||||||
|
Object = Pointer, // coded with & symbol
|
||||||
|
Native = Object | 0x0001,
|
||||||
|
String = Object | 0x0002,
|
||||||
|
Array = Object | 0x0004,
|
||||||
|
Function = Object | 0x0008,
|
||||||
|
Context = Object | 0x0010,
|
||||||
|
Instance = Object | 0x0020,
|
||||||
|
Class = Object | 0x0100,
|
||||||
|
Method = Class | Function,
|
||||||
|
PreValue = Object | 0x0200,
|
||||||
|
|
||||||
|
Module = 0xFFFF
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 18
|
||||||
|
VisualStudioVersion = 18.0.11123.170 d18.0
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Qrakhen.Qamp.Core", "Qrakhen.Qamp.Core\Qrakhen.Qamp.Core.csproj", "{CA6F4D4E-AF35-4CA6-BC53-C83277EEE46C}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Qrakhen.Qamp.CLI", "Qrakhen.Qamp.CLI\Qrakhen.Qamp.CLI.csproj", "{1D355F76-9D35-4CB2-BF8C-944B0842BC30}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{CA6F4D4E-AF35-4CA6-BC53-C83277EEE46C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{CA6F4D4E-AF35-4CA6-BC53-C83277EEE46C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{CA6F4D4E-AF35-4CA6-BC53-C83277EEE46C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{CA6F4D4E-AF35-4CA6-BC53-C83277EEE46C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{1D355F76-9D35-4CB2-BF8C-944B0842BC30}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{1D355F76-9D35-4CB2-BF8C-944B0842BC30}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{1D355F76-9D35-4CB2-BF8C-944B0842BC30}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{1D355F76-9D35-4CB2-BF8C-944B0842BC30}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {87910094-C957-4430-AF2E-4586EC943D0A}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||||
|
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/USE_INDENT_FROM_VS/@EntryValue">False</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpUseContinuousIndentInsideBracesMigration/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=06E0B5326F2C40588720EE747CA86EE5/Field/=table/Order/@EntryValue">0</s:Int64>
|
||||||
|
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=12037952FB3E3C5483C767E0ED2BC2D1/Field/=size/Order/@EntryValue">1</s:Int64>
|
||||||
|
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=12037952FB3E3C5483C767E0ED2BC2D1/Field/=table/Order/@EntryValue">0</s:Int64>
|
||||||
|
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=4162F7CFEE9D0D57818E26901B9038A6/Field/=columns/Order/@EntryValue">1</s:Int64>
|
||||||
|
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=4162F7CFEE9D0D57818E26901B9038A6/Field/=info/Order/@EntryValue">2</s:Int64>
|
||||||
|
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=4162F7CFEE9D0D57818E26901B9038A6/Field/=table/Order/@EntryValue">0</s:Int64>
|
||||||
|
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=6A12208A3767B459875BAE2F16DB1F12/Field/=col/Order/@EntryValue">1</s:Int64>
|
||||||
|
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=6A12208A3767B459875BAE2F16DB1F12/Field/=value/Order/@EntryValue">2</s:Int64>
|
||||||
|
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=73DB1A9093BC3555944CB6697D7ADC0E/Field/=size/Order/@EntryValue">1</s:Int64>
|
||||||
|
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=73DB1A9093BC3555944CB6697D7ADC0E/Field/=table/Order/@EntryValue">0</s:Int64>
|
||||||
|
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=75FF192AAB2D7B5F9F2A2885642C9E65/Field/=alias/Order/@EntryValue">1</s:Int64>
|
||||||
|
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=75FF192AAB2D7B5F9F2A2885642C9E65/Field/=table/Order/@EntryValue">0</s:Int64>
|
||||||
|
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=76F670AC8ED1C558AC4117F02DC3C7C5/Field/=col/Order/@EntryValue">1</s:Int64>
|
||||||
|
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=76F670AC8ED1C558AC4117F02DC3C7C5/Field/=null/Order/@EntryValue">3</s:Int64>
|
||||||
|
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=76F670AC8ED1C558AC4117F02DC3C7C5/Field/=table/Order/@EntryValue">0</s:Int64>
|
||||||
|
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=76F670AC8ED1C558AC4117F02DC3C7C5/Field/=type/Order/@EntryValue">2</s:Int64>
|
||||||
|
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=8BA1F9C8C49B765C85965EDCC8FE8B44/Field/=col/Order/@EntryValue">0</s:Int64>
|
||||||
|
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=8BA1F9C8C49B765C85965EDCC8FE8B44/Field/=null/Order/@EntryValue">2</s:Int64>
|
||||||
|
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=8BA1F9C8C49B765C85965EDCC8FE8B44/Field/=type/Order/@EntryValue">1</s:Int64>
|
||||||
|
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=AFD61AF46C31125AA2138D9D6109A63F/Field/=alias/Order/@EntryValue">1</s:Int64>
|
||||||
|
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=AFD61AF46C31125AA2138D9D6109A63F/Field/=table/Order/@EntryValue">0</s:Int64>
|
||||||
|
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=B672577C8CC1EF51B0753529E944F88B/Field/=table/Order/@EntryValue">1</s:Int64>
|
||||||
|
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=B672577C8CC1EF51B0753529E944F88B/Field/=view/Order/@EntryValue">0</s:Int64>
|
||||||
|
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=B76382EA2248705C87F2C8C21758FA26/Field/=table/Order/@EntryValue">0</s:Int64>
|
||||||
|
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=C2827EE21D7EE85591A2FDCFB158EB07/Field/=size/Order/@EntryValue">1</s:Int64>
|
||||||
|
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=C2827EE21D7EE85591A2FDCFB158EB07/Field/=table/Order/@EntryValue">0</s:Int64>
|
||||||
|
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=DDE95C42052B4557924B6E68F16EC8F1/Field/=size/Order/@EntryValue">1</s:Int64>
|
||||||
|
<s:Int64 x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=DDE95C42052B4557924B6E68F16EC8F1/Field/=table/Order/@EntryValue">0</s:Int64>
|
||||||
|
<s:Boolean x:Key="/Default/UserDictionary/Words/=Qamp/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||||
Loading…
Reference in New Issue