lots of things, but mainly finish runner. gonna be a ton of debugging now.
This commit is contained in:
parent
2747c17c25
commit
f584435bff
|
|
@ -2,9 +2,10 @@
|
|||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<BaseOutputPath>..\Build\</BaseOutputPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
# Todo:
|
||||
|
||||
### Make Digester OOP-ish
|
||||
- inherit from a base digester that has the continouus stack / reader references as protected information
|
||||
- one digester for every job area (Loops, Functions, Variables, etc.)
|
||||
- time is not as essential here as I want to have the executables pre-compiled to OpCodes anyway
|
||||
|
||||
### - Finish runner
|
||||
- add interpretation for all remaining op codes
|
||||
- make variable operations faster with optimized inline lambda expressions
|
||||
- better debugging for the instruction set
|
||||
|
||||
### CLI / IDE / VSC
|
||||
- fix 'IDE' in CLI
|
||||
- create syntax highlighting extension for vs code (if i ever manage to)
|
||||
|
||||
### Smaller Goals
|
||||
- think of a cleaner way to handle Obj (pointers specifically)
|
||||
- add classic sqript dialect
|
||||
- fix console "IDE"
|
||||
- split runtime, digester and core into separate projects
|
||||
|
|
@ -17,7 +17,7 @@ public abstract class Expander<T> :
|
|||
{
|
||||
protected T[] Data;
|
||||
|
||||
public long Count { get; protected set; }= 0;
|
||||
public long Count { get; protected set; } = 0;
|
||||
public long Capacity => Data.Length;
|
||||
|
||||
protected Expander(int capacity = 0x10)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,10 @@
|
|||
|
||||
namespace Qrakhen.Qamp.Core.Collections;
|
||||
|
||||
/// <summary>
|
||||
/// Basically just a normal array, but implements <see cref="IGetSet{TKey, TValue}"/> to be used with <see cref="Pointer{T}"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public class FixedArray<T> : IEnumerable<T>, IGetSet<long, T>
|
||||
{
|
||||
protected T[] Data;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,46 @@
|
|||
namespace Qrakhen.Qamp.Core.Collections;
|
||||
|
||||
/// <summary>
|
||||
/// Dynamic Pointer able to point to any object implementing the <see cref="IGetSet{long, TValue}"/> interface.
|
||||
/// </summary>
|
||||
public class Pointer<T>
|
||||
{
|
||||
protected IGetSet<long, T> Target;
|
||||
public long Ptr;
|
||||
|
||||
public Pointer(IGetSet<long, T> target, long pointer = 0)
|
||||
{
|
||||
Target = target;
|
||||
Ptr = pointer;
|
||||
}
|
||||
|
||||
public Pointer<T> Branch(long delta = 0)
|
||||
{
|
||||
return new Pointer<T>(Target, Ptr + delta);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the next item from <see cref="Target"/> and increases the pointer by 1.
|
||||
/// </summary>
|
||||
public T Next() => Target.Get(Ptr++);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the value of <see cref="Target"/> at <paramref name="position"/>.
|
||||
/// </summary>
|
||||
public void Set(long position, T value) => Target.Set(position, value);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the value of <see cref="Target"/> at the current <see cref="Ptr"/> location.
|
||||
/// </summary>
|
||||
public void Set(T value) => Set(Ptr, value);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value of <see cref="Target"/> at <paramref name="position"/>.
|
||||
/// </summary>
|
||||
public T Get(long position) => Target.Get(position);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value of <see cref="Target"/> at the current <see cref="Ptr"/> location.
|
||||
/// </summary>
|
||||
public T Get() => Get(Ptr);
|
||||
}
|
||||
|
|
@ -2,7 +2,13 @@
|
|||
|
||||
namespace Qrakhen.Qamp.Core.Collections;
|
||||
|
||||
public class PopStack<T> : IEnumerable<T>, IPop<T>, IPeekable<T>
|
||||
/// <summary>
|
||||
/// Pop-Only stack with a fixed array that is gradually reduced.
|
||||
/// </summary>
|
||||
public class PopStack<T> :
|
||||
IEnumerable<T>,
|
||||
IPop<T>,
|
||||
IPeekable<T>
|
||||
{
|
||||
private readonly T[] _data;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,15 @@
|
|||
namespace Qrakhen.Qamp.Core.Collections;
|
||||
|
||||
public class StackRegister<T> : Expander<T>, IToArray<T>
|
||||
/// <summary>
|
||||
/// Push-only Stack that is based on <see cref="Expander{T}"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public class PushStack<T> :
|
||||
Expander<T>,
|
||||
IPush<long, T>,
|
||||
IToArray<T>
|
||||
{
|
||||
public StackRegister(int initialCapacity = 0x10)
|
||||
public PushStack(int initialCapacity = 0x10)
|
||||
{
|
||||
Data = new T[initialCapacity];
|
||||
}
|
||||
|
|
@ -15,20 +22,22 @@ public class StackRegister<T> : Expander<T>, IToArray<T>
|
|||
return default;
|
||||
}
|
||||
|
||||
public void Add(T[] array)
|
||||
public void Push(T[] array)
|
||||
{
|
||||
foreach (T value in array) {
|
||||
Add(value);
|
||||
}
|
||||
}
|
||||
|
||||
public void Add(IEnumerable<T> array)
|
||||
public void Push(IEnumerable<T> array)
|
||||
{
|
||||
foreach (T value in array) {
|
||||
Add(value);
|
||||
}
|
||||
}
|
||||
|
||||
public long Push(T value) => Add(value);
|
||||
|
||||
public bool TryGet(long index, out T value)
|
||||
{
|
||||
value = default;
|
||||
|
|
@ -1,7 +1,14 @@
|
|||
namespace Qrakhen.Qamp.Core.Collections;
|
||||
using System.Collections;
|
||||
|
||||
namespace Qrakhen.Qamp.Core.Collections;
|
||||
|
||||
/// <summary>
|
||||
/// Dictionary wrapper implementing <see cref="IGetSet{TKey, TValue}"/>.
|
||||
/// </summary>
|
||||
public class Register<TKey, TValue> :
|
||||
IGetSet<TKey, TValue>
|
||||
IGetSet<TKey, TValue>,
|
||||
IToArray<TValue>,
|
||||
IEnumerable<KeyValuePair<TKey, TValue>>
|
||||
where TKey : notnull
|
||||
{
|
||||
private readonly Dictionary<TKey, TValue> _data = new();
|
||||
|
|
@ -19,7 +26,21 @@ public class Register<TKey, TValue> :
|
|||
return key;
|
||||
}
|
||||
|
||||
public bool TryGet(TKey key, out TValue? value) => _data.TryGetValue(key, out value);
|
||||
public TValue Get(TKey key) => _data[key];
|
||||
public void Set(TKey key, TValue value) => _data[key] = value;
|
||||
public bool Has(TKey key) => _data.ContainsKey(key);
|
||||
|
||||
public TValue[] ToArray() => _data.Values.ToArray();
|
||||
|
||||
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
|
||||
{
|
||||
foreach (var item in _data)
|
||||
yield return item;
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,15 @@
|
|||
namespace Qrakhen.Qamp.Core.Collections;
|
||||
|
||||
/// <summary>
|
||||
/// Stack-like implementation with a few added features like seeking and setting values.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public class StackLike<T> : Expander<T>,
|
||||
IPush<long, T>,
|
||||
IPop<T>,
|
||||
ISeekable<long, T>,
|
||||
IPeekable<T>
|
||||
IPeekable<T>,
|
||||
IToArray<T>
|
||||
{
|
||||
public long Position => Count;
|
||||
|
||||
|
|
@ -39,4 +44,14 @@ public class StackLike<T> : Expander<T>,
|
|||
|
||||
public bool CanPeek(int delta = 0)
|
||||
=> Count + delta > -1 && Count + delta < Count;
|
||||
|
||||
/// <summary>
|
||||
/// Decimates this stack down to the given position, overwriting everything beyond that when pushing in the future.
|
||||
/// </summary>
|
||||
public void Decimate(long position)
|
||||
{
|
||||
Count = position;
|
||||
}
|
||||
|
||||
public T[] ToArray() => Data.ToArray();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,10 +4,15 @@ namespace Qrakhen.Qamp.Core.Compilation.Builders;
|
|||
|
||||
public class Builder
|
||||
{
|
||||
public Builder? Outer;
|
||||
public Builder? Outer { get; init; }
|
||||
public FunctionBuilder Function = new();
|
||||
|
||||
public StackLike<Local> Locals = [];
|
||||
public StackLike<Outer> Outers = [];
|
||||
public int ScopeDepth = 0;
|
||||
|
||||
public Builder(Builder? outer = null)
|
||||
{
|
||||
Outer = outer;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
namespace Qrakhen.Qamp.Core.Compilation.Builders;
|
||||
|
||||
public interface IBuilder<TOut>
|
||||
{
|
||||
public TOut Build();
|
||||
}
|
||||
|
|
@ -4,10 +4,10 @@ using Qrakhen.Qamp.Core.Values;
|
|||
|
||||
namespace Qrakhen.Qamp.Core.Compilation.Builders;
|
||||
|
||||
public readonly struct SegmentBuilder() : IDebug<string>
|
||||
public class SegmentBuilder() : IBuilder<Segment>, IDebug<string>
|
||||
{
|
||||
public readonly StackRegister<Instruction> Instructions = new();
|
||||
public readonly StackRegister<Value> Constants = new();
|
||||
public readonly PushStack<Instruction> Instructions = new();
|
||||
public readonly PushStack<Value> Constants = new();
|
||||
|
||||
public Segment Build()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -37,6 +37,11 @@ public class DigesterProvider
|
|||
public object ExpressionDigester; // etc.
|
||||
}
|
||||
|
||||
public abstract class xDigester
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public class Digester : ISteppable<Token>
|
||||
{
|
||||
private readonly CompilerState _compiler;
|
||||
|
|
@ -105,7 +110,7 @@ public class Digester : ISteppable<Token>
|
|||
{
|
||||
Emit(Op.Return);
|
||||
Function function = Builder.Function.Build();
|
||||
Builder = Builder.Outer!;
|
||||
Builder = Builder.Outer;
|
||||
return function;
|
||||
}
|
||||
|
||||
|
|
@ -114,8 +119,8 @@ public class Digester : ISteppable<Token>
|
|||
_logger.Method();
|
||||
if (Match(T.Class)) DeclareClass();
|
||||
else if (Match(T.Function)) DeclareFunction();
|
||||
else if (Match(T.Var)) DeclareVariable();
|
||||
else Statement();
|
||||
else if (Match(T.Var)) DeclareVariable();
|
||||
else Statement();
|
||||
}
|
||||
|
||||
internal void Statement()
|
||||
|
|
@ -129,15 +134,19 @@ public class Digester : ISteppable<Token>
|
|||
else if (Match(T.Return)) Return();
|
||||
else if (Match(T.TypeOf)) TypeOf();
|
||||
else if (Match(T.Print)) Print();
|
||||
else if (Match(T.PrintStack)) PrintStack();
|
||||
else if (Match(T.PrintGlobals)) PrintGlobals();
|
||||
else if (Match(T.PrintExpr)) PrintExpr();
|
||||
else if (Match(T.Export)) Export();
|
||||
else if (Match(T.Import)) Import();
|
||||
else if (Match(T.ContextOpen)) Context();
|
||||
else StatementExpression();
|
||||
else Expression();
|
||||
}
|
||||
|
||||
internal void StatementExpression()
|
||||
internal void Expression()
|
||||
{
|
||||
Expression();
|
||||
_logger.Method();
|
||||
ParseExpression();
|
||||
Consume(T.Semicolon, "Expected ';' after statement");
|
||||
Emit(Op.Pop); // in an expression statement, we definitely do not want to remain the value on stack,
|
||||
// as it would fuck over the entire stack for the rest of the execution.
|
||||
|
|
@ -146,7 +155,7 @@ public class Digester : ISteppable<Token>
|
|||
// a tad bit older me: it's a terrible idea as return a = b; wouldn't work anymore
|
||||
}
|
||||
|
||||
internal void Expression() => WeightedDigest(Weight.Assign);
|
||||
internal void ParseExpression() => WeightedDigest(Weight.Assign);
|
||||
|
||||
internal void Context()
|
||||
{
|
||||
|
|
@ -221,7 +230,7 @@ public class Digester : ISteppable<Token>
|
|||
ExpressionParser.Variable(this, name, false);
|
||||
Consume(T.ContextOpen, "Expected '{' for class body declaration.", false);
|
||||
while (!Check(T.ContextClose) && !Check(T.Eof))
|
||||
DeclareMethod();
|
||||
DeclareMember();
|
||||
|
||||
Consume(T.ContextClose, "Expected '}' to end class body declaration.", false);
|
||||
Emit(Op.Pop);
|
||||
|
|
@ -241,8 +250,9 @@ public class Digester : ISteppable<Token>
|
|||
DefineVariable(global);
|
||||
}
|
||||
|
||||
internal void DeclareMethod()
|
||||
internal void DeclareMember()
|
||||
{
|
||||
// todo: only methods allowed atm, add fields and shit too
|
||||
_logger.Method();
|
||||
Consume(T.Identifier, "Expected method name.", false);
|
||||
long identifier = IdentifierConstant(Previous.Value!);
|
||||
|
|
@ -258,7 +268,7 @@ public class Digester : ISteppable<Token>
|
|||
_logger.Method();
|
||||
long global = ParseVariable();
|
||||
if (Match(T.Equal))
|
||||
Expression();
|
||||
ParseExpression();
|
||||
else
|
||||
Emit(Op.Null);
|
||||
Consume(T.Semicolon, "missing ; after variable declaration");
|
||||
|
|
@ -279,7 +289,7 @@ public class Digester : ISteppable<Token>
|
|||
{
|
||||
_logger.Method();
|
||||
Consume(T.GroupOpen, "Expected '(' after if");
|
||||
Expression();
|
||||
ParseExpression();
|
||||
Consume(T.GroupClose, "Expected ')' after condition");
|
||||
|
||||
long then = EmitJump(Op.JumpIfFalse);
|
||||
|
|
@ -308,7 +318,7 @@ public class Digester : ISteppable<Token>
|
|||
_logger.Method();
|
||||
long start = Function.CurrentInstruction;
|
||||
Consume(T.GroupOpen, "Expected while condition to be placed inside parentheses. Missing '('.", false);
|
||||
Expression();
|
||||
ParseExpression();
|
||||
Consume(T.GroupClose, "Expected while condition to be placed inside parentheses. Missing ')'.", false);
|
||||
long exit = EmitJump(Op.JumpIfFalse);
|
||||
Emit(Op.Pop);
|
||||
|
|
@ -335,13 +345,13 @@ public class Digester : ISteppable<Token>
|
|||
} else if (Match(T.Var)) {
|
||||
DeclareVariable();
|
||||
} else {
|
||||
StatementExpression();
|
||||
Expression();
|
||||
}
|
||||
|
||||
long start = Function.CurrentInstruction;
|
||||
long exit = -1;
|
||||
if (!Match(T.Semicolon)) {
|
||||
Expression();
|
||||
ParseExpression();
|
||||
Consume(T.Semicolon, "Expected ';' after condition");
|
||||
exit = EmitJump(Op.JumpIfFalse);
|
||||
Emit(Op.Pop);
|
||||
|
|
@ -350,7 +360,7 @@ public class Digester : ISteppable<Token>
|
|||
if (!Match(T.GroupClose)) {
|
||||
long body = EmitJump(Op.Jump);
|
||||
long increment = Function.Segment.Instructions.Count;
|
||||
Expression();
|
||||
ParseExpression();
|
||||
Emit(Op.Pop);
|
||||
Consume(T.GroupClose, "Expected ')' after for clause");
|
||||
|
||||
|
|
@ -378,15 +388,36 @@ public class Digester : ISteppable<Token>
|
|||
internal void Print()
|
||||
{
|
||||
_logger.Method();
|
||||
Expression();
|
||||
ParseExpression();
|
||||
Consume(T.Semicolon, "Expected ';' after print call.");
|
||||
Emit(Op.Print);
|
||||
}
|
||||
|
||||
internal void PrintGlobals()
|
||||
{
|
||||
_logger.Method();
|
||||
Consume(T.Semicolon, "Expected ';' after print call.");
|
||||
Emit(Op.PrintGlobals);
|
||||
}
|
||||
|
||||
internal void PrintStack()
|
||||
{
|
||||
_logger.Method();
|
||||
Consume(T.Semicolon, "Expected ';' after print call.");
|
||||
Emit(Op.PrintStack);
|
||||
}
|
||||
|
||||
internal void PrintExpr()
|
||||
{
|
||||
_logger.Method();
|
||||
Consume(T.Semicolon, "Expected ';' after print call.");
|
||||
Emit(Op.PrintExpr);
|
||||
}
|
||||
|
||||
internal void TypeOf()
|
||||
{
|
||||
_logger.Method();
|
||||
Expression();
|
||||
ParseExpression();
|
||||
Emit(Op.Typeof);
|
||||
}
|
||||
|
||||
|
|
@ -403,9 +434,9 @@ public class Digester : ISteppable<Token>
|
|||
while (Builder.Locals.Count > 0 &&
|
||||
Builder.Locals.Peek().Depth > Builder.ScopeDepth) {
|
||||
if (Builder.Locals.Peek().IsCaptured) {
|
||||
Emit(OpCode.CloseUpvalue);
|
||||
Emit(Op.CloseOuter);
|
||||
} else {
|
||||
Emit(OpCode.Pop);
|
||||
Emit(Op.Pop);
|
||||
}
|
||||
Builder.Locals.Pop();
|
||||
}
|
||||
|
|
@ -425,17 +456,16 @@ public class Digester : ISteppable<Token>
|
|||
DefineVariable(ParseVariable());
|
||||
} while (Match(T.Comma));
|
||||
}
|
||||
|
||||
Consume(T.GroupClose, "Expected ')' after argument list.");
|
||||
Consume(T.ContextOpen, "Expected function body");
|
||||
Block();
|
||||
Function function = FinishBuilder();
|
||||
Emit(Op.Closure);
|
||||
Emit(MakeConstant(Obj.Create(function)));
|
||||
Emit(Op.Context);
|
||||
EmitDynamic(MakeConstant(Obj.Create(function)));
|
||||
for (int i = 0; i < function.OuterCount; i++) {
|
||||
Outer outer = next.Outers.Get(i);
|
||||
Emit(outer.IsLocal ? 1 : 0);
|
||||
Emit(outer.Index);
|
||||
EmitDynamic(outer.Index);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -551,12 +581,12 @@ public class Digester : ISteppable<Token>
|
|||
}
|
||||
}
|
||||
|
||||
internal byte ArgumentList()
|
||||
internal byte Arguments()
|
||||
{
|
||||
byte count = 0;
|
||||
if (!Check(T.GroupClose)) {
|
||||
do {
|
||||
Expression();
|
||||
ParseExpression();
|
||||
if (count++ >= 0xFF)
|
||||
ErrorAtCurrent("How many arguments do you need?");
|
||||
} while (Match(T.Comma));
|
||||
|
|
@ -621,7 +651,8 @@ public class Digester : ISteppable<Token>
|
|||
/// </summary>
|
||||
internal void EmitDynamic(byte[] data)
|
||||
{
|
||||
Emit((byte)(data.Length | 0x80));
|
||||
if (data.Length > 1)
|
||||
Emit((byte)(data.Length | 0x80)); // 0x80 flag for length marker, anything else is direct value
|
||||
Emit(data);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -52,8 +52,8 @@ public static class ExpressionParser
|
|||
|
||||
static void Call(Digester digester, bool canAssign)
|
||||
{
|
||||
byte count = digester.ArgumentList();
|
||||
digester.Emit(OpCode.Call, count);
|
||||
byte count = digester.Arguments();
|
||||
digester.Emit(OpCode.Invoke, count);
|
||||
}
|
||||
|
||||
static void Dot(Digester digester, bool canAssign)
|
||||
|
|
@ -62,14 +62,14 @@ public static class ExpressionParser
|
|||
long name = digester.IdentifierConstant(digester.Previous.Value);
|
||||
|
||||
if (canAssign && digester.Match(TokenType.Equal)) {
|
||||
digester.Expression();
|
||||
digester.EmitDynamic(OpCode.SetProperty, name);
|
||||
digester.ParseExpression();
|
||||
digester.EmitDynamic(OpCode.SetMember, name);
|
||||
} else if (digester.Match(TokenType.GroupOpen)) {
|
||||
Byte argCount = 0; //argumentList();
|
||||
digester.Emit(OpCode.Invoke, name.GetBytes());
|
||||
digester.EmitDynamic(argCount);
|
||||
byte argCount = digester.Arguments();
|
||||
digester.EmitDynamic(OpCode.InvokeMember, name);
|
||||
digester.Emit(argCount);
|
||||
} else {
|
||||
digester.EmitDynamic(OpCode.GetProperty, name);
|
||||
digester.EmitDynamic(OpCode.GetMember, name);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -92,7 +92,7 @@ public static class ExpressionParser
|
|||
|
||||
static void Group(Digester digester, bool canAssign)
|
||||
{
|
||||
digester.Expression();
|
||||
digester.ParseExpression();
|
||||
digester.Consume(TokenType.GroupClose, "Expected ')' after expression.");
|
||||
}
|
||||
|
||||
|
|
@ -100,7 +100,7 @@ public static class ExpressionParser
|
|||
{
|
||||
int length = 0;
|
||||
while (!digester.Check(TokenType.ArrayClose)) {
|
||||
digester.Expression();
|
||||
digester.ParseExpression();
|
||||
length++;
|
||||
if (digester.Check(TokenType.Comma))
|
||||
digester.Next();
|
||||
|
|
@ -113,19 +113,19 @@ public static class ExpressionParser
|
|||
|
||||
static void ArrayAdd(Digester digester, bool canAssign)
|
||||
{
|
||||
digester.Expression();
|
||||
digester.ParseExpression();
|
||||
digester.Emit(OpCode.ArrayAdd);
|
||||
}
|
||||
|
||||
static void Index(Digester digester, bool canAssign)
|
||||
{
|
||||
digester.Expression();
|
||||
digester.ParseExpression();
|
||||
digester.Consume(TokenType.ArrayClose, "expected ] after array accessor");
|
||||
if (canAssign && digester.Match(TokenType.ArrayAdd)) {
|
||||
digester.Expression();
|
||||
digester.ParseExpression();
|
||||
digester.Emit(OpCode.ArrayAdd);
|
||||
} else if (canAssign && digester.Match(TokenType.Equal)) {
|
||||
digester.Expression();
|
||||
digester.ParseExpression();
|
||||
digester.Emit(OpCode.ArraySet);
|
||||
} else {
|
||||
digester.Emit(OpCode.ArrayGet);
|
||||
|
|
@ -183,7 +183,7 @@ public static class ExpressionParser
|
|||
}
|
||||
|
||||
if (canAssign && digester.Match(TokenType.Equal)) {
|
||||
digester.Expression();
|
||||
digester.ParseExpression();
|
||||
digester.Emit(Set);
|
||||
digester.EmitDynamic(variable);
|
||||
} else {
|
||||
|
|
@ -211,9 +211,9 @@ public static class ExpressionParser
|
|||
Variable(digester, "this", false);
|
||||
if (digester.Match(TokenType.GroupOpen))
|
||||
{
|
||||
byte arguments = digester.ArgumentList();
|
||||
byte arguments = digester.Arguments();
|
||||
Variable(digester, "base", false);
|
||||
digester.Emit(OpCode.BaseCall);
|
||||
digester.Emit(OpCode.InvokeBase);
|
||||
digester.Emit(arguments);
|
||||
} else {
|
||||
Variable(digester, "base", false);
|
||||
|
|
@ -294,7 +294,7 @@ public static class ExpressionParser
|
|||
_rules[TokenType.Export] = new Rule(null, null, Weight.None);
|
||||
_rules[TokenType.Import] = new Rule(null, null, Weight.None);
|
||||
_rules[TokenType.Return] = new Rule(null, null, Weight.None);
|
||||
_rules[TokenType.Super] = new Rule(Base, null, Weight.None);
|
||||
_rules[TokenType.Base] = new Rule(Base, null, Weight.None);
|
||||
_rules[TokenType.This] = new Rule(This, null, Weight.None);
|
||||
_rules[TokenType.True] = new Rule(Literal, null, Weight.None);
|
||||
_rules[TokenType.Var] = new Rule(null, null, Weight.None);
|
||||
|
|
|
|||
|
|
@ -1,14 +1,12 @@
|
|||
using Qrakhen.Qamp.Core.Tokenization;
|
||||
using Qrakhen.Qamp.Core.Values;
|
||||
|
||||
using V = Qrakhen.Qamp.Core.Values.ValueType;
|
||||
|
||||
namespace Qrakhen.Qamp.Core;
|
||||
|
||||
public class QampException(string message, TokenPosition position = default)
|
||||
public class QampException(string message, object? context = null)
|
||||
: Exception(message)
|
||||
{
|
||||
public readonly TokenPosition Position = position;
|
||||
public readonly object? Context = context;
|
||||
}
|
||||
|
||||
public class ReaderException(string message, IReader<Token> reader)
|
||||
|
|
@ -23,14 +21,14 @@ public class TokenException(string message, IReader<Token> reader, Token token)
|
|||
public readonly Token Token = token;
|
||||
}
|
||||
|
||||
public class ExecutionException(string message, TokenPosition position = default)
|
||||
: QampException(message, position);
|
||||
public class RuntimeException(string message, object? context = null)
|
||||
: QampException(message, context);
|
||||
|
||||
public class DivisionByZeroException(string? message, TokenPosition position = default)
|
||||
: ExecutionException(message ?? "Can not divide by zero", position);
|
||||
public class DivisionByZeroException(string? message, object? context = null)
|
||||
: RuntimeException(message ?? "Can not divide by zero", context);
|
||||
|
||||
public class ConversionException(string message, Value value, TokenPosition position = default)
|
||||
: ExecutionException(message, position)
|
||||
public class ConversionException(string message, Value value, object? context = null)
|
||||
: RuntimeException(message, context)
|
||||
{
|
||||
public readonly Value Value = value;
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
using Qrakhen.Qamp.Core.Collections;
|
||||
using Qrakhen.Qamp.Core.Values;
|
||||
using String = Qrakhen.Qamp.Core.Values.Objects.String;
|
||||
|
||||
namespace Qrakhen.Qamp.Core.Execution;
|
||||
|
||||
public class InstructionPtr : Pointer<Instruction>
|
||||
{
|
||||
public Segment Segment;
|
||||
|
||||
public Instruction Instruction => Segment.Instructions[Ptr];
|
||||
|
||||
public InstructionPtr(Segment segment, int position = 0) : base(segment.Instructions, position)
|
||||
{
|
||||
Segment = segment;
|
||||
}
|
||||
|
||||
public long NextLong()
|
||||
{
|
||||
long value = Segment.ReadLong(Ptr);
|
||||
Ptr += sizeof(long);
|
||||
return value;
|
||||
}
|
||||
|
||||
public Value NextConstant()
|
||||
{
|
||||
return Segment.Constants[NextDynamic()];
|
||||
}
|
||||
|
||||
public String NextString()
|
||||
{
|
||||
Value value = NextConstant();
|
||||
if (value.IsString)
|
||||
return value.Ptr.As<String>()!;
|
||||
throw new Exception($"Expected string, got {value}");
|
||||
}
|
||||
|
||||
public long NextDynamic()
|
||||
{
|
||||
var result = Segment.ReadDynamic(Ptr, out int read);
|
||||
Ptr += read;
|
||||
return result;
|
||||
}
|
||||
|
||||
public String? GetStringConstant(long offset)
|
||||
=> Segment.Constants[offset].Ptr.Value as String;
|
||||
|
||||
public static Instruction operator +(InstructionPtr ptr, int value)
|
||||
=> ptr.Segment.Instructions[ptr.Ptr + value];
|
||||
|
||||
public static Instruction operator -(InstructionPtr ptr, int value)
|
||||
=> ptr.Segment.Instructions[ptr.Ptr - value];
|
||||
|
||||
public static InstructionPtr operator ++(InstructionPtr ptr)
|
||||
{
|
||||
ptr.Ptr++;
|
||||
return ptr;
|
||||
}
|
||||
|
||||
public static InstructionPtr operator --(InstructionPtr ptr)
|
||||
{
|
||||
ptr.Ptr--;
|
||||
return ptr;
|
||||
}
|
||||
}
|
||||
|
|
@ -2,62 +2,87 @@
|
|||
|
||||
public enum OpCode
|
||||
{
|
||||
None = 0,
|
||||
Constant,
|
||||
Null,
|
||||
True,
|
||||
False,
|
||||
Pop,
|
||||
Export,
|
||||
Import,
|
||||
GetLocal,
|
||||
SetLocal,
|
||||
GetGlobal,
|
||||
DefineGlobal,
|
||||
SetGlobal,
|
||||
GetOuter,
|
||||
SetOuter,
|
||||
GetProperty,
|
||||
SetProperty,
|
||||
Property,
|
||||
Base,
|
||||
Equal,
|
||||
Greater,
|
||||
Less,
|
||||
Add,
|
||||
Subtract,
|
||||
Multiply,
|
||||
Divide,
|
||||
Modulo,
|
||||
BitwiseAnd,
|
||||
BitwiseOr,
|
||||
BitwiseXor,
|
||||
BitwiseNot,
|
||||
BitwiseLeft,
|
||||
BitwiseRight,
|
||||
Increment,
|
||||
Decrement,
|
||||
Not,
|
||||
Negate,
|
||||
Ref,
|
||||
Array,
|
||||
ArrayGet,
|
||||
ArraySet,
|
||||
ArrayAdd,
|
||||
ArrayRemove,
|
||||
Print,
|
||||
PrintExpr,
|
||||
Typeof,
|
||||
Jump,
|
||||
JumpIfFalse,
|
||||
Loop,
|
||||
Call,
|
||||
Invoke,
|
||||
BaseCall,
|
||||
Closure,
|
||||
CloseUpvalue,
|
||||
Return,
|
||||
Class,
|
||||
Inherit,
|
||||
Method
|
||||
F_Mask = 0xF0,
|
||||
|
||||
None = 0x00,
|
||||
Constant = 0x01,
|
||||
Null = 0x02,
|
||||
Pop = 0x03,
|
||||
Cast = 0x04,
|
||||
|
||||
False = 0x0e,
|
||||
True = 0x0f,
|
||||
|
||||
Val = 0x10,
|
||||
Ref = 0x11,
|
||||
|
||||
SetGlobal = 0x20,
|
||||
GetGlobal = 0x21,
|
||||
GetLocal = 0x22,
|
||||
SetLocal = 0x23,
|
||||
GetOuter = 0x24,
|
||||
SetOuter = 0x25,
|
||||
GetMember = 0x26,
|
||||
SetMember = 0x27,
|
||||
|
||||
DefineGlobal = 0x30,
|
||||
CloseOuter = 0x31,
|
||||
|
||||
AssignValue = 0x40, // unsure
|
||||
AssignReference = 0x41, // unsure
|
||||
|
||||
F_Operation = 0x60,
|
||||
Add = 0x60,
|
||||
Subtract = 0x61,
|
||||
Multiply = 0x62,
|
||||
Divide = 0x63,
|
||||
Modulo = 0x64,
|
||||
BitwiseAnd = 0x65,
|
||||
BitwiseOr = 0x66,
|
||||
BitwiseXor = 0x67,
|
||||
BitwiseLeft = 0x68,
|
||||
BitwiseRight = 0x69,
|
||||
F_Unary = 0x0a,
|
||||
Not = 0x6a,
|
||||
Negate = 0x6b,
|
||||
BitwiseNot = 0x6c,
|
||||
|
||||
F_Compare = 0x50,
|
||||
Equal = 0x50,
|
||||
Greater = 0x51,
|
||||
Less = 0x52,
|
||||
|
||||
Increment = 0x70,
|
||||
Decrement = 0x71,
|
||||
|
||||
Array = 0xc0,
|
||||
ArrayGet = 0xc1,
|
||||
ArraySet = 0xc2,
|
||||
ArrayAdd = 0xc3,
|
||||
ArrayRemove = 0xc4,
|
||||
|
||||
Return = 0x80,
|
||||
Jump = 0x81,
|
||||
JumpIfFalse = 0x82,
|
||||
Loop = 0x83,
|
||||
Invoke = 0x84,
|
||||
InvokeBase = 0x85,
|
||||
InvokeMember = 0x86,
|
||||
Context = 0x87,
|
||||
|
||||
Class = 0xa0,
|
||||
Member = 0xa1,
|
||||
Base = 0xa2,
|
||||
Inherit = 0xa3,
|
||||
Method = 0xa4,
|
||||
Static = 0xa5,
|
||||
|
||||
Print = 0xe0,
|
||||
PrintStack = 0xe1,
|
||||
PrintGlobals = 0xe2,
|
||||
PrintExpr = 0xe3,
|
||||
Typeof = 0xef,
|
||||
|
||||
Export = 0xfe, // probably shouldnt be an op code but rather be done during digestion
|
||||
Import = 0xff, // probably shouldnt be an op code but rather be done during digestion
|
||||
}
|
||||
|
|
@ -1,165 +1,47 @@
|
|||
using Qrakhen.Qamp.Core.Collections;
|
||||
using Qrakhen.Qamp.Core.Compilation;
|
||||
using Qrakhen.Qamp.Core.Logging;
|
||||
using Qrakhen.Qamp.Core.Tokenization;
|
||||
using Qrakhen.Qamp.Core.Values;
|
||||
using Qrakhen.Qamp.Core.Values.Objects;
|
||||
using System.Xml.Linq;
|
||||
using String = Qrakhen.Qamp.Core.Values.Objects.String;
|
||||
|
||||
namespace Qrakhen.Qamp.Core.Execution;
|
||||
|
||||
using Op = OpCode;
|
||||
|
||||
/*
|
||||
* Todo:
|
||||
* - Make Digester OOP-ish
|
||||
* - inherit from a base digester that has the continouus stack / reader references as protected information
|
||||
* - one digester for every job area (Loops, Functions, Variables, etc.)
|
||||
* - time is not as essential here as I want to have the executables pre-compiled to OpCodes anyway
|
||||
* - Think of a cleaner way to handle Obj (Pointers specifically)
|
||||
* - Finish runner
|
||||
* - add interpretation for all remaining op codes
|
||||
* - make variable operations faster with optimized inline lambda expressions
|
||||
* - better debugging for the instruction set
|
||||
* - Add classic sqript dialect
|
||||
* - Fix console "IDE"
|
||||
* - Split Runner, Digester and Core into separate projects
|
||||
*/
|
||||
|
||||
/// <summary>
|
||||
/// Dynamic Pointer able to point to any object implementing the <see cref="IGetSet{long, TValue}"/> interface.
|
||||
/// </summary>
|
||||
public class Pointer<T>
|
||||
{
|
||||
protected IGetSet<long, T> Target;
|
||||
public long Ptr;
|
||||
|
||||
public Pointer(IGetSet<long, T> target, long pointer = 0)
|
||||
{
|
||||
Target = target;
|
||||
Ptr = pointer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the next item from <see cref="Target"/> and increases the pointer by 1.
|
||||
/// </summary>
|
||||
public T Next() => Target.Get(Ptr++);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the value of <see cref="Target"/> at <paramref name="position"/>.
|
||||
/// </summary>
|
||||
public void Set(long position, T value) => Target.Set(position, value);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the value of <see cref="Target"/> at the current <see cref="Ptr"/> location.
|
||||
/// </summary>
|
||||
public void Set(T value) => Set(Ptr, value);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value of <see cref="Target"/> at <paramref name="position"/>.
|
||||
/// </summary>
|
||||
public T Get(long position) => Target.Get(position);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value of <see cref="Target"/> at the current <see cref="Ptr"/> location.
|
||||
/// </summary>
|
||||
public T Get() => Get(Ptr);
|
||||
}
|
||||
|
||||
// this is all a bit cheesy imho
|
||||
public class InstructionPtr : Pointer<Instruction>
|
||||
{
|
||||
public Segment Segment;
|
||||
|
||||
public InstructionPtr(Segment segment, int position = 0) : base(segment.Instructions, position)
|
||||
{
|
||||
Segment = segment;
|
||||
}
|
||||
|
||||
public long NextLong()
|
||||
{
|
||||
long value = Segment.ReadLong(Ptr);
|
||||
Ptr += sizeof(long);
|
||||
return value;
|
||||
}
|
||||
|
||||
public Value NextConstant()
|
||||
{
|
||||
return Segment.Constants[NextDynamic()];
|
||||
}
|
||||
|
||||
public long NextDynamic()
|
||||
{
|
||||
int length = Segment.Read(Ptr) ^ 0x80;
|
||||
Ptr++;
|
||||
byte[] data = new byte[8];
|
||||
for (int i = 0; i < length; i++)
|
||||
data[i] = Segment.Read(Ptr++);
|
||||
return data.ToInt64();
|
||||
}
|
||||
|
||||
|
||||
public String? GetStringConstant(long offset)
|
||||
{
|
||||
return Segment.Constants[offset].Ptr.Value as String;
|
||||
}
|
||||
|
||||
public Instruction Instruction => Segment.Instructions[Ptr];
|
||||
|
||||
public static Instruction operator +(InstructionPtr ptr, int value)
|
||||
=> ptr.Segment.Instructions[ptr.Ptr + value];
|
||||
|
||||
public static Instruction operator -(InstructionPtr ptr, int value)
|
||||
=> ptr.Segment.Instructions[ptr.Ptr - value];
|
||||
|
||||
public static InstructionPtr operator ++(InstructionPtr ptr)
|
||||
{
|
||||
ptr.Ptr++;
|
||||
return ptr;
|
||||
}
|
||||
|
||||
public static InstructionPtr operator --(InstructionPtr ptr)
|
||||
{
|
||||
ptr.Ptr--;
|
||||
return ptr;
|
||||
}
|
||||
}
|
||||
using T = Values.ValueType;
|
||||
|
||||
public class Call
|
||||
{
|
||||
public Closure Closure;
|
||||
public Context Context;
|
||||
public InstructionPtr Instruction;
|
||||
public Pointer<Value> StackPtr;
|
||||
|
||||
public Call(Closure closure, StackLike<Value> stack, long stackOffset)
|
||||
public Call(Context context, StackLike<Value> stack, long stackOffset)
|
||||
{
|
||||
Closure = closure;
|
||||
Instruction = new InstructionPtr(Closure.Function.Segment);
|
||||
Context = context;
|
||||
Instruction = new InstructionPtr(Context.Function.Segment);
|
||||
StackPtr = new Pointer<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;
|
||||
private 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;
|
||||
private StackLike<Value> Stack;
|
||||
private long Cursor => Stack.Position;
|
||||
|
||||
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>();
|
||||
private List<Outer> Outers = [];
|
||||
|
||||
private Register<string, Value> Globals = new();
|
||||
private Register<string, Value> Imports = new();
|
||||
private Register<string, Value> Exports = new();
|
||||
|
||||
public Runner(Options options)
|
||||
{
|
||||
|
|
@ -172,39 +54,74 @@ public class Runner : IDisposable
|
|||
{
|
||||
_logger.Method();
|
||||
using Reader reader = new Reader(stream);
|
||||
Digester digester = new Digester(reader);
|
||||
Compilation.Digester digester = new(reader);
|
||||
Function function = digester.Digest();
|
||||
File.WriteAllBytes("func.sqi", function.Segment.Serialize());
|
||||
Closure closure = new Closure(function);
|
||||
Context closure = new Context(function);
|
||||
Push(Obj.Create(closure));
|
||||
Call(closure, 0);
|
||||
return Interpret();
|
||||
Invoke(closure, 0);
|
||||
return Execute();
|
||||
}
|
||||
|
||||
private ExecutionResult Interpret()
|
||||
private bool TestFlag(Op code, Op flag) => (code & Op.F_Mask) == flag;
|
||||
|
||||
private ExecutionResult Execute()
|
||||
{
|
||||
_logger.Method();
|
||||
Call call = Calls.Peek(-1);
|
||||
do {
|
||||
Op opCode = call.Instruction.Next();
|
||||
_logger.Verbose($"OpCode: {opCode}");
|
||||
switch (opCode) {
|
||||
case Op.Constant: Push(call.Instruction.NextConstant()); break;
|
||||
case Op.Pop: Pop(); break;
|
||||
|
||||
case Op.Not: OpNot(); break;
|
||||
case Op.Add: OpAdd(); break;
|
||||
case Op.Subtract: OpSubtract(); break;
|
||||
case Op.Divide: OpDivide(); break;
|
||||
case Op.Modulo: OpModulo(); break;
|
||||
case Op.Multiply: OpMultiply(); break;
|
||||
case Op.Negate: OpNegate(); break;
|
||||
case Op.BitwiseOr: OpBitwiseOr(); break;
|
||||
case Op.BitwiseAnd: OpBitwiseAnd(); break;
|
||||
case Op.BitwiseXor: OpBitwiseXor(); break;
|
||||
case Op.BitwiseNot: OpBitwiseNot(); break;
|
||||
case Op.BitwiseLeft: OpBitwiseLeft(); break;
|
||||
case Op.BitwiseRight: OpBitwiseRight(); break;
|
||||
if (TestFlag(opCode, Op.F_Operation)) {
|
||||
Value result = ValueOperation.Resolve(PopOperation(opCode));
|
||||
Push(result);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (TestFlag(opCode, Op.F_Compare)) {
|
||||
Value result = ValueOperation.Resolve(PopOperation(opCode));
|
||||
Push(result);
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (opCode) {
|
||||
case Op.Constant: Push(call.Instruction.NextConstant()); break;
|
||||
case Op.Pop: Pop(); break;
|
||||
case Op.True: Push(Value.True); break;
|
||||
case Op.False: Push(Value.False); break;
|
||||
|
||||
case Op.GetGlobal: {
|
||||
string? name = call.Instruction.GetStringConstant(call.Instruction.NextDynamic())?.Value;
|
||||
if (string.IsNullOrEmpty(name))
|
||||
throw new RuntimeException($"tried to set global variable with empty name");
|
||||
if (!Globals.Has(name))
|
||||
Push(Value.Void);
|
||||
else
|
||||
Push(Globals[name]);
|
||||
_logger.Verbose($"got global {name} ({Peek()})");
|
||||
break;
|
||||
}
|
||||
|
||||
case Op.SetGlobal: {
|
||||
string? name = call.Instruction.GetStringConstant(call.Instruction.NextDynamic())?.Value;
|
||||
if (string.IsNullOrEmpty(name))
|
||||
throw new RuntimeException($"tried to set global variable with empty name");
|
||||
if (!Globals.Has(name))
|
||||
throw new RuntimeException($"tried to set a value of non-existing global variable");
|
||||
Globals[name] = Pop();
|
||||
_logger.Verbose($"set global {name} = {Globals[name]}");
|
||||
break;
|
||||
}
|
||||
|
||||
case Op.DefineGlobal: {
|
||||
string? name = call.Instruction.GetStringConstant(call.Instruction.NextDynamic())?.Value;
|
||||
if (string.IsNullOrEmpty(name))
|
||||
throw new RuntimeException($"tried to define global variable with empty name");
|
||||
Globals[name] = Pop();
|
||||
_logger.Verbose($"defined global {name} as {Globals[name]}");
|
||||
break;
|
||||
}
|
||||
|
||||
case Op.GetLocal: {
|
||||
long slot = call.Instruction.NextDynamic();
|
||||
|
|
@ -220,38 +137,158 @@ public class Runner : IDisposable
|
|||
break;
|
||||
}
|
||||
|
||||
case Op.DefineGlobal: {
|
||||
string? name = call.Instruction.GetStringConstant(call.Instruction.NextDynamic())?.Value;
|
||||
if (string.IsNullOrEmpty(name))
|
||||
throw new ExecutionException($"tried to define global variable with empty name");
|
||||
Globals[name] = Pop();
|
||||
_logger.Verbose($"defined global {name} as {Globals[name]}");
|
||||
case Op.GetOuter: {
|
||||
long slot = call.Instruction.NextDynamic();
|
||||
Push(call.Context.Outers[slot].Value);
|
||||
break;
|
||||
}
|
||||
|
||||
case Op.SetGlobal: {
|
||||
string? name = call.Instruction.GetStringConstant(call.Instruction.NextDynamic())?.Value;
|
||||
if (string.IsNullOrEmpty(name))
|
||||
throw new ExecutionException($"tried to set global variable with empty name");
|
||||
if (!Globals.Has(name))
|
||||
throw new ExecutionException($"tried to set a value of non-existing global variable");
|
||||
Globals[name] = Pop();
|
||||
_logger.Verbose($"set global {name} = {Globals[name]}");
|
||||
case Op.SetOuter: {
|
||||
long slot = call.Instruction.NextDynamic();
|
||||
call.Context.Outers[slot].Value = Peek();
|
||||
break;
|
||||
}
|
||||
|
||||
case Op.GetGlobal: {
|
||||
string? name = call.Instruction.GetStringConstant(call.Instruction.NextDynamic())?.Value;
|
||||
if (string.IsNullOrEmpty(name))
|
||||
throw new ExecutionException($"tried to set global variable with empty name");
|
||||
if (!Globals.Has(name))
|
||||
Push(Value.Void);
|
||||
else
|
||||
Push(Globals[name]);
|
||||
_logger.Verbose($"got global {name} ({Peek()})");
|
||||
case Op.GetMember: {
|
||||
Value value = Peek();
|
||||
if (!value.Is(T.Instance))
|
||||
return Error("Only instances have members.");
|
||||
Instance instance = value.Ptr.As<Instance>()!;
|
||||
string name = call.Instruction.NextString().Value!;
|
||||
if (instance.Values.TryGet(name, out Value member)) {
|
||||
Pop(); // remove instance from stack
|
||||
Push(member);
|
||||
} else if (!BindMethod(instance.Class, name)) {
|
||||
return Error($"Could not bind member {name} to instance {instance} of class {instance.Class.Name}!");
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case Op.SetMember: {
|
||||
Value value = Peek(-2);
|
||||
if (!value.Is(T.Instance))
|
||||
return Error("Only instances have members.");
|
||||
Instance instance = value.Ptr.As<Instance>()!;
|
||||
string name = call.Instruction.NextString().Value!;
|
||||
instance.Values.Set(name, Peek());
|
||||
Value move = Pop();
|
||||
Pop(); // remove instance from stack
|
||||
Push(move);
|
||||
break;
|
||||
}
|
||||
|
||||
case Op.Base: {
|
||||
string name = call.Instruction.NextString().Value!;
|
||||
Class @class = Pop().Ptr.As<Class>()!;
|
||||
if (!BindMethod(@class, name))
|
||||
return Error($"Could not bind member {name} to base class {@class.Name}");
|
||||
break;
|
||||
}
|
||||
|
||||
case Op.Jump: {
|
||||
long delta = call.Instruction.NextLong();
|
||||
call.Instruction.Ptr += delta;
|
||||
break;
|
||||
}
|
||||
|
||||
case Op.JumpIfFalse: {
|
||||
long delta = call.Instruction.NextLong();
|
||||
if (Peek(0).IsFalsy)
|
||||
call.Instruction.Ptr += delta;
|
||||
break;
|
||||
}
|
||||
|
||||
case Op.Loop: {
|
||||
long delta = call.Instruction.NextLong();
|
||||
call.Instruction.Ptr -= delta;
|
||||
break;
|
||||
}
|
||||
|
||||
case Op.Invoke: {
|
||||
byte count = call.Instruction.Next();
|
||||
if (!Invoke(Peek(-(count + 1)), count))
|
||||
return Error($"Could not invoke function.");
|
||||
call = Calls.Peek();
|
||||
break;
|
||||
}
|
||||
|
||||
case Op.InvokeMember: {
|
||||
string name = call.Instruction.NextString().Value!;
|
||||
byte argumentCount = call.Instruction.Next();
|
||||
if (!Invoke(name, argumentCount))
|
||||
return Error($"Could not invoke member {name}.");
|
||||
call = Calls.Peek();
|
||||
break;
|
||||
}
|
||||
|
||||
case Op.InvokeBase: {
|
||||
string name = call.Instruction.NextString().Value!;
|
||||
byte argumentCount = call.Instruction.Next();
|
||||
Class @class = Pop().Ptr.As<Class>()!;
|
||||
if (!Invoke(@class, name, argumentCount))
|
||||
return Error($"Could not invoke member {name} of base class {@class.Name}!");
|
||||
call = Calls.Peek();
|
||||
break;
|
||||
}
|
||||
|
||||
case Op.Context: {
|
||||
Function function = call.Instruction.NextConstant().Ptr.As<Function>()!;
|
||||
Context context = new Context(function);
|
||||
Push(Obj.Create(context));
|
||||
for (int i = 0; i < context.Function.OuterCount; i++) {
|
||||
Outer outer = context.Outers[i];
|
||||
bool isLocal = call.Instruction.Next() == 1;
|
||||
long index = call.Instruction.NextDynamic();
|
||||
if (isLocal)
|
||||
context.Outers.Add(CaptureOuter(call.StackPtr.Branch(index)));
|
||||
else
|
||||
context.Outers.Add(call.Context.Outers[index]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case Op.CloseOuter: {
|
||||
CloseOuters(new Pointer<Value>(Stack, Cursor));
|
||||
Pop();
|
||||
break;
|
||||
}
|
||||
|
||||
case Op.Return: // todo: not done
|
||||
Value result = Pop();
|
||||
CloseOuters(call.StackPtr.Branch());
|
||||
Calls.Pop();
|
||||
if (Calls.Count == 0) {
|
||||
Pop();
|
||||
return ExecutionResult.OK;
|
||||
}
|
||||
Stack.Decimate(call.StackPtr.Ptr);
|
||||
Push(result);
|
||||
call = Calls.Peek();
|
||||
break;
|
||||
|
||||
case Op.Class: {
|
||||
Push(Obj.Create(new Class(call.Instruction.NextString().Value!)));
|
||||
break;
|
||||
}
|
||||
|
||||
case Op.Inherit: {
|
||||
Value @class = Peek(-2);
|
||||
if (!@class.Is(T.Class))
|
||||
return Error($"Can not inherit from {@class}, value must be of type Class.");
|
||||
Class baseClass = @class.Ptr.As<Class>()!;
|
||||
Class inheritingClass = Peek().Ptr.As<Class>()!;
|
||||
foreach (var member in baseClass.Members) {
|
||||
if (!inheritingClass.Members.Has(member.Key))
|
||||
inheritingClass.Members.Add(member.Key, member.Value);
|
||||
}
|
||||
Pop(); //
|
||||
break;
|
||||
}
|
||||
|
||||
case Op.Method:
|
||||
DefineMember(call.Instruction.NextString().Value!);
|
||||
break;
|
||||
|
||||
case Op.Typeof: {
|
||||
Value value = Pop();
|
||||
|
|
@ -265,17 +302,22 @@ public class Runner : IDisposable
|
|||
break;
|
||||
}
|
||||
|
||||
case Op.Return: // todo: not done
|
||||
Value result = Pop();
|
||||
Calls.Pop();
|
||||
if (Calls.Count == 0) {
|
||||
Pop();
|
||||
return ExecutionResult.OK;
|
||||
case Op.PrintStack: {
|
||||
for (int i = 0; i < Cursor; i++) {
|
||||
Console.WriteLine($"{Stack[i].ToString(true)}");
|
||||
}
|
||||
Cursor = call.StackPtr.Ptr; // well find a better way...
|
||||
Push(result);
|
||||
call = Calls.Peek();
|
||||
break;
|
||||
}
|
||||
|
||||
case Op.PrintGlobals: {
|
||||
foreach (var value in Globals) {
|
||||
Console.WriteLine($"{value.Key}: {value.Value.ToString(true)}");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
return Error($"Unexpected or not yet supported OpCode {opCode}!");
|
||||
}
|
||||
|
||||
} while (call.Instruction.Segment.Instructions.Length > call.Instruction.Ptr);
|
||||
|
|
@ -283,7 +325,34 @@ public class Runner : IDisposable
|
|||
return ExecutionResult.OK;
|
||||
}
|
||||
|
||||
private bool Call(Closure closure, int argumentCount)
|
||||
private Outer CaptureOuter(Pointer<Value> target)
|
||||
{
|
||||
Outer? outer = null;
|
||||
for (int i = Outers.Count; i-->0;) {
|
||||
outer = Outers[i];
|
||||
if (outer.Target.Ptr <= target.Ptr)
|
||||
break;
|
||||
}
|
||||
|
||||
if (outer != null && outer.Target.Ptr == target.Ptr)
|
||||
return outer;
|
||||
|
||||
return new Outer(target);
|
||||
}
|
||||
|
||||
private void CloseOuters(Pointer<Value> last)
|
||||
{
|
||||
for (int i = Outers.Count;i-->0;) {
|
||||
Outer outer = Outers[i];
|
||||
if (outer.IsClosed)
|
||||
continue;
|
||||
if (outer.Target.Ptr < last.Ptr)
|
||||
return;
|
||||
outer.Close();
|
||||
}
|
||||
}
|
||||
|
||||
private bool Invoke(Context closure, int argumentCount)
|
||||
{
|
||||
_logger.Method();
|
||||
if (argumentCount != closure.Function.ArgumentCount)
|
||||
|
|
@ -297,6 +366,90 @@ public class Runner : IDisposable
|
|||
return true;
|
||||
}
|
||||
|
||||
private bool Invoke(string methodName, int argumentCount)
|
||||
{
|
||||
Value value = Peek(-argumentCount);
|
||||
if (!value.Is(T.Instance)) {
|
||||
Error($"Can not call {methodName} on value of {value}");
|
||||
return false;
|
||||
}
|
||||
|
||||
Instance instance = value.Ptr.As<Instance>()!;
|
||||
if (instance.Values.TryGet(methodName, out Value method))
|
||||
return Invoke(method, argumentCount);
|
||||
|
||||
return Invoke(instance.Class, methodName, argumentCount);
|
||||
}
|
||||
|
||||
private bool Invoke(Class @class, string methodName, int argumentCount)
|
||||
{
|
||||
if (@class.Members.TryGet(methodName, out Value method))
|
||||
return Invoke(method, argumentCount);
|
||||
Error($"Could not call method {methodName} on class {@class.Name}: Member does not exist");
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool Invoke(Value value, int argumentCount)
|
||||
{
|
||||
if (!value.IsObj) {
|
||||
Error($"Can only call values of object type! Tried to call {value.ToString(true)}", value);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (value.Is(T.Method)) {
|
||||
Method method = value.Ptr.As<Method>()!;
|
||||
Stack.Set(Cursor - method.Function.ArgumentCount - 1, method.Receiver);
|
||||
return Invoke(method, method.Function.ArgumentCount);
|
||||
} else if (value.Is(T.Context)) {
|
||||
Context? context = value.Ptr.As<Context>()!;
|
||||
return Invoke(context, context.Function.ArgumentCount);
|
||||
} else if (value.Is(T.Native)) {
|
||||
throw new NotImplementedException("i dont really want globals in this language so lets see");
|
||||
} else if (value.Is(T.Class)) {
|
||||
Class? @class = value.Ptr.As<Class>()!;
|
||||
Stack.Set(Cursor - argumentCount - 1, Instantiate(@class));
|
||||
if (@class.Members.TryGet(@class.Name, out Value ctor)) {
|
||||
Context? context = ctor.Ptr.As<Context>()!;
|
||||
return Invoke(context, argumentCount);
|
||||
} else if (argumentCount != 0) {
|
||||
Error($"{@class.Name}() expects 0 arguments but received {argumentCount}");
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool BindMethod(Class @class, string name)
|
||||
{
|
||||
if (!@class.Members.TryGet(name, out Value value)) {
|
||||
Error($"Undefined member '{name}' on class {@class.Name}");
|
||||
return false;
|
||||
}
|
||||
|
||||
Method method = new Method(value.Ptr.As<Function>()!, Peek());
|
||||
Pop(); // remove instance from stack
|
||||
Push(Obj.Create(method));
|
||||
return true;
|
||||
}
|
||||
|
||||
private Value Instantiate(Class @class)
|
||||
{
|
||||
Instance instance = new Instance(@class);
|
||||
// todo: init fields etc here too
|
||||
return Obj.Create(instance);
|
||||
}
|
||||
|
||||
private void DefineMember(string name)
|
||||
{
|
||||
Value method = Peek();
|
||||
Class @class = Peek(-2).Ptr.As<Class>()!;
|
||||
@class.Members.Set(name, method);
|
||||
Pop();
|
||||
}
|
||||
|
||||
private Value Peek(int delta = -1)
|
||||
{
|
||||
_logger.Method();
|
||||
|
|
@ -315,92 +468,23 @@ public class Runner : IDisposable
|
|||
Stack.Push(value);
|
||||
}
|
||||
|
||||
private void OpNot()
|
||||
{
|
||||
Value value = Pop();
|
||||
Push(ValueOperation.Not(value));
|
||||
}
|
||||
|
||||
private void OpAdd()
|
||||
private Operation PopOperation(Op code)
|
||||
{
|
||||
_logger.Method(code);
|
||||
if ((code & Op.F_Unary) == Op.F_Unary)
|
||||
return new Operation(code, Pop(), default);
|
||||
Value right = Pop();
|
||||
Value left = Pop();
|
||||
Push(ValueOperation.Add(left, right));
|
||||
return new Operation(code, left, right);
|
||||
}
|
||||
|
||||
private void OpSubtract()
|
||||
private ExecutionResult Error(string message, object? context = null, bool @throw = true)
|
||||
{
|
||||
Value right = Pop();
|
||||
Value left = Pop();
|
||||
Push(ValueOperation.Subtract(left, right));
|
||||
}
|
||||
|
||||
private void OpDivide()
|
||||
{
|
||||
Value right = Pop();
|
||||
Value left = Pop();
|
||||
Push(ValueOperation.Divide(left, right));
|
||||
}
|
||||
|
||||
private void OpModulo()
|
||||
{
|
||||
Value right = Pop();
|
||||
Value left = Pop();
|
||||
Push(ValueOperation.Modulo(left, right));
|
||||
}
|
||||
|
||||
private void OpMultiply()
|
||||
{
|
||||
Value right = Pop();
|
||||
Value left = Pop();
|
||||
Push(ValueOperation.Multiply(left, right));
|
||||
}
|
||||
|
||||
private void OpNegate()
|
||||
{
|
||||
Value value = Pop();
|
||||
Push(ValueOperation.Negate(value));
|
||||
}
|
||||
|
||||
private void OpBitwiseOr()
|
||||
{
|
||||
Value right = Pop();
|
||||
Value left = Pop();
|
||||
Push(ValueOperation.BitwiseOr(left, right));
|
||||
}
|
||||
|
||||
private void OpBitwiseAnd()
|
||||
{
|
||||
Value right = Pop();
|
||||
Value left = Pop();
|
||||
Push(ValueOperation.BitwiseAnd(left, right));
|
||||
}
|
||||
|
||||
private void OpBitwiseXor()
|
||||
{
|
||||
Value right = Pop();
|
||||
Value left = Pop();
|
||||
Push(ValueOperation.BitwiseXor(left, right));
|
||||
}
|
||||
|
||||
private void OpBitwiseLeft()
|
||||
{
|
||||
Value right = Pop();
|
||||
Value left = Pop();
|
||||
Push(ValueOperation.BitwiseLeft(left, right));
|
||||
}
|
||||
|
||||
private void OpBitwiseRight()
|
||||
{
|
||||
Value right = Pop();
|
||||
Value left = Pop();
|
||||
Push(ValueOperation.BitwiseRight(left, right));
|
||||
}
|
||||
|
||||
private void OpBitwiseNot()
|
||||
{
|
||||
Value value = Pop();
|
||||
Push(ValueOperation.BitwiseInvert(value));
|
||||
_logger.Method(message);
|
||||
_logger.Error(message, context);
|
||||
if (@throw)
|
||||
throw new QampException(message, context);
|
||||
return ExecutionResult.Execution;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
|
@ -409,6 +493,11 @@ public class Runner : IDisposable
|
|||
}
|
||||
}
|
||||
|
||||
public class GarbageCollector
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public enum ExecutionResult
|
||||
{
|
||||
OK = 0x0000,
|
||||
|
|
|
|||
|
|
@ -29,18 +29,27 @@ public class Segment(
|
|||
}
|
||||
|
||||
/// <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)
|
||||
/// Although this always returns a long, the actually stored data in the instruction set has a dynamic width
|
||||
/// (anything between 1 and 9 bytes, 1 if it's just a byte, and 1 + n, where n is up to 8 bytes).<br/>
|
||||
/// If the first byte read is masked with 0x80, it means that this byte represents the amount of bytes that
|
||||
/// shall be read. If it is not, the byte is returned directly (for smaller offsets).
|
||||
/// </summary>
|
||||
/// <param name="offset"></param>
|
||||
/// <returns></returns>
|
||||
public long ReadDynamic(long offset, out byte length)
|
||||
/// <remarks>
|
||||
/// All of this is done to compress compiled instruction size.
|
||||
/// Todo: It will need to be tested wheter the cost on performance is stronger than anticipated.
|
||||
/// </remarks>
|
||||
public long ReadDynamic(long offset, out int read)
|
||||
{
|
||||
length = (byte)(Read(offset) ^ 0x80);
|
||||
byte[] bytes = new byte[8];
|
||||
for (int i = 0; i < length; i++) {
|
||||
bytes[i] = Instructions[offset + 1 + i];
|
||||
}
|
||||
return bytes.ToInt64();
|
||||
byte value = Read(offset);
|
||||
read = 1;
|
||||
if (value < 0x80) // 0x80 flag for length, less than 0x80 means direct read
|
||||
return value;
|
||||
int length = value ^ 0x80;
|
||||
read += length;
|
||||
var data = new byte[8];
|
||||
for (int i = 0; i < length; i++)
|
||||
data[i] = Read(offset + i);
|
||||
return data.ToInt64();
|
||||
}
|
||||
|
||||
public short ReadShort(long offset) => BitConverter.ToInt16(Read(offset, 2));
|
||||
|
|
|
|||
|
|
@ -0,0 +1,197 @@
|
|||
using Qrakhen.Qamp.Core.Collections;
|
||||
using Qrakhen.Qamp.Core.Compilation;
|
||||
using Qrakhen.Qamp.Core.Logging;
|
||||
using Qrakhen.Qamp.Core.Values;
|
||||
|
||||
namespace Qrakhen.Qamp.Core.Execution;
|
||||
|
||||
public readonly record struct Operation(OpCode OpCode, Value Left, Value Right);
|
||||
|
||||
public delegate Value OperationHandler(Operation operation);
|
||||
|
||||
public static class ValueOperation
|
||||
{
|
||||
private static readonly ILogger _logger = LoggerService.Get(nameof(ValueOperation));
|
||||
|
||||
private static readonly Register<OpCode, OperationHandler> _operations;
|
||||
|
||||
public static Value Resolve(Operation operation)
|
||||
{
|
||||
_logger.Method($"{operation.Left} {operation.OpCode} {operation.Right}");
|
||||
return _operations[operation.OpCode].Invoke(operation);
|
||||
}
|
||||
|
||||
static ValueOperation()
|
||||
{
|
||||
_operations = new Register<OpCode, OperationHandler>();
|
||||
_operations.Add(OpCode.Equal, Equal);
|
||||
_operations.Add(OpCode.Greater, Greater);
|
||||
_operations.Add(OpCode.Less, Less);
|
||||
_operations.Add(OpCode.Not, Not);
|
||||
_operations.Add(OpCode.Add, Add);
|
||||
_operations.Add(OpCode.Subtract, Subtract);
|
||||
_operations.Add(OpCode.Divide, Divide);
|
||||
_operations.Add(OpCode.Modulo, Modulo);
|
||||
_operations.Add(OpCode.Multiply, Multiply);
|
||||
_operations.Add(OpCode.Negate, Negate);
|
||||
_operations.Add(OpCode.BitwiseOr, BitwiseOr);
|
||||
_operations.Add(OpCode.BitwiseAnd, BitwiseAnd);
|
||||
_operations.Add(OpCode.BitwiseLeft, BitwiseLeft);
|
||||
_operations.Add(OpCode.BitwiseRight, BitwiseRight);
|
||||
_operations.Add(OpCode.BitwiseNot, BitwiseInvert);
|
||||
_operations.Add(OpCode.BitwiseXor, BitwiseXor);
|
||||
}
|
||||
|
||||
public static Value Equal(Operation operation)
|
||||
{
|
||||
Value a = operation.Left;
|
||||
Value b = operation.Right;
|
||||
Assert.NotVoid(a, b);
|
||||
Assert.IsPrimitive(a, b);
|
||||
return new Value(a.Dynamic == b.Dynamic);
|
||||
}
|
||||
|
||||
public static Value Greater(Operation operation)
|
||||
{
|
||||
Value a = operation.Left;
|
||||
Value b = operation.Right;
|
||||
Assert.NotVoid(a, b);
|
||||
Assert.IsPrimitive(a, b);
|
||||
return new Value(a.Dynamic > b.Dynamic);
|
||||
}
|
||||
|
||||
public static Value Less(Operation operation)
|
||||
{
|
||||
Value a = operation.Left;
|
||||
Value b = operation.Right;
|
||||
Assert.NotVoid(a, b);
|
||||
Assert.IsPrimitive(a, b);
|
||||
return new Value(a.Dynamic < b.Dynamic);
|
||||
}
|
||||
|
||||
public static Value Not(Operation operation)
|
||||
{
|
||||
Value value = operation.Left;
|
||||
_logger.Method($"{value}");
|
||||
Assert.NotVoid(value);
|
||||
ulong inner = value.Unsigned;
|
||||
return new Value(inner == 0);
|
||||
}
|
||||
|
||||
public static Value Add(Operation operation)
|
||||
{
|
||||
Value a = operation.Left;
|
||||
Value b = operation.Right;
|
||||
Assert.NotVoid(a, b);
|
||||
if (a.IsString)
|
||||
return Values.Objects.String.Make($"{a}{b}");
|
||||
Assert.IsPrimitive(a, b);
|
||||
bool convert = a.Type != b.Type;
|
||||
return new Value(a.Dynamic + b.Dynamic);
|
||||
}
|
||||
|
||||
public static Value Subtract(Operation operation)
|
||||
{
|
||||
Value a = operation.Left;
|
||||
Value b = operation.Right;
|
||||
Assert.NotVoid(a, b);
|
||||
Assert.IsPrimitive(a, b);
|
||||
return new Value(a.Dynamic - b.Dynamic);
|
||||
}
|
||||
|
||||
public static Value Divide(Operation operation)
|
||||
{
|
||||
Value a = operation.Left;
|
||||
Value b = operation.Right;
|
||||
Assert.NotVoid(a, b);
|
||||
Assert.IsPrimitive(a, b);
|
||||
Assert.NotZero(b);
|
||||
return new Value(a.Dynamic / b.Dynamic);
|
||||
}
|
||||
|
||||
public static Value Modulo(Operation operation)
|
||||
{
|
||||
Value a = operation.Left;
|
||||
Value b = operation.Right;
|
||||
Assert.NotVoid(a, b);
|
||||
Assert.IsPrimitive(a, b);
|
||||
Assert.NotZero(b);
|
||||
return new Value(a.Dynamic % b.Dynamic);
|
||||
}
|
||||
|
||||
public static Value Multiply(Operation operation)
|
||||
{
|
||||
Value a = operation.Left;
|
||||
Value b = operation.Right;
|
||||
Assert.NotVoid(a, b);
|
||||
Assert.IsPrimitive(a, b);
|
||||
return new Value(a.Dynamic * b.Dynamic);
|
||||
}
|
||||
|
||||
public static Value Negate(Operation operation)
|
||||
{
|
||||
Value value = operation.Left;
|
||||
Assert.NotVoid(value);
|
||||
Assert.IsPrimitive(value);
|
||||
return new Value(-value.Dynamic);
|
||||
}
|
||||
|
||||
public static Value BitwiseOr(Operation operation)
|
||||
{
|
||||
Value a = operation.Left;
|
||||
Value b = operation.Right;
|
||||
Assert.NotVoid(a, b);
|
||||
Assert.IsPrimitive(a, b);
|
||||
Assert.IsInteger(a, b);
|
||||
return new Value(a.Dynamic | b.Dynamic);
|
||||
}
|
||||
|
||||
public static Value BitwiseAnd(Operation operation)
|
||||
{
|
||||
Value a = operation.Left;
|
||||
Value b = operation.Right;
|
||||
Assert.NotVoid(a, b);
|
||||
Assert.IsPrimitive(a, b);
|
||||
Assert.IsInteger(a, b);
|
||||
return new Value(a.Dynamic & b.Dynamic);
|
||||
}
|
||||
|
||||
public static Value BitwiseXor(Operation operation)
|
||||
{
|
||||
Value a = operation.Left;
|
||||
Value b = operation.Right;
|
||||
Assert.NotVoid(a, b);
|
||||
Assert.IsPrimitive(a, b);
|
||||
Assert.IsInteger(a, b);
|
||||
return new Value(a.Dynamic ^ b.Dynamic);
|
||||
}
|
||||
|
||||
public static Value BitwiseLeft(Operation operation)
|
||||
{
|
||||
Value a = operation.Left;
|
||||
Value b = operation.Right;
|
||||
Assert.NotVoid(a, b);
|
||||
Assert.IsPrimitive(a, b);
|
||||
Assert.IsInteger(a, b);
|
||||
return new Value(a.Dynamic << (int)(b.Dynamic ?? 0));
|
||||
}
|
||||
|
||||
public static Value BitwiseRight(Operation operation)
|
||||
{
|
||||
Value a = operation.Left;
|
||||
Value b = operation.Right;
|
||||
Assert.NotVoid(a, b);
|
||||
Assert.IsPrimitive(a, b);
|
||||
Assert.IsInteger(a, b);
|
||||
return new Value(a.Dynamic >> (int)(b.Dynamic ?? 0));
|
||||
}
|
||||
|
||||
public static Value BitwiseInvert(Operation operation)
|
||||
{
|
||||
Value a = operation.Left;
|
||||
Assert.NotVoid(a);
|
||||
Assert.IsPrimitive(a);
|
||||
Assert.IsInteger(a);
|
||||
return new Value(~a.Dynamic);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
namespace Qrakhen.Qamp.Core.Injector;
|
||||
|
||||
/// <summary>
|
||||
/// Class used to export custom .dll implementations as binary instruction files (*.sqi).
|
||||
/// </summary>
|
||||
public class Injector
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public enum Scope
|
||||
{
|
||||
/// <summary>Within the global scope of the file that is importing this library</summary>
|
||||
Global,
|
||||
/// <summary>Within the scope of the module this library exports</summary>
|
||||
Module,
|
||||
/// <summary>An extension of the built-in <see cref="Values.Value"/> type</summary>
|
||||
Extension
|
||||
}
|
||||
|
|
@ -5,5 +5,6 @@
|
|||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<BaseOutputPath>..\Build\</BaseOutputPath>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ public class DefaultDialect : Dialect
|
|||
Define(Ref, "ref");
|
||||
Define(Function, "function");
|
||||
Define(Class, "class");
|
||||
Define(Super, "base");
|
||||
Define(Base, "base");
|
||||
Define(TypeOf, "typeof");
|
||||
Define(Print, "print");
|
||||
Define(Import, "import");
|
||||
|
|
@ -109,7 +109,7 @@ public class ClassicDialect : DefaultDialect
|
|||
Define(Return, "<:");
|
||||
Define(Ref, "*&");
|
||||
Define(Function, "fq");
|
||||
Define(Super, "^~");
|
||||
Define(Base, "^~");
|
||||
Define(TypeOf, "?:");
|
||||
Define(Print, "::");
|
||||
Define(Import, "<!");
|
||||
|
|
|
|||
|
|
@ -32,13 +32,19 @@ internal static partial class ReaderPatterns
|
|||
Keywords["var"] = Var;
|
||||
Keywords["while"] = While;
|
||||
Keywords["do"] = Do;
|
||||
Keywords["typeof"] = TypeOf;
|
||||
Keywords["ref"] = Ref;
|
||||
Keywords["function"] = Function;
|
||||
Keywords["funqtion"] = Function;
|
||||
Keywords["fq"] = Function;
|
||||
Keywords["funq"] = Function;
|
||||
Keywords["return"] = Return;
|
||||
Keywords["class"] = Class;
|
||||
Keywords["super"] = Super;
|
||||
Keywords["base"] = Base;
|
||||
Keywords["typeof"] = TypeOf;
|
||||
Keywords["print"] = Print;
|
||||
Keywords["globals"] = PrintGlobals;
|
||||
Keywords["stack"] = PrintStack;
|
||||
Keywords["expr"] = PrintExpr;
|
||||
Keywords["import"] = Import;
|
||||
Keywords["export"] = Export;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -87,10 +87,13 @@ public enum TokenType
|
|||
Ref,
|
||||
Function,
|
||||
Class,
|
||||
Super,
|
||||
Base,
|
||||
|
||||
TypeOf,
|
||||
Print,
|
||||
PrintStack,
|
||||
PrintGlobals,
|
||||
PrintExpr,
|
||||
TypeOf,
|
||||
|
||||
Import,
|
||||
Export
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
namespace Qrakhen.Qamp.Core.Values.Objects;
|
||||
|
||||
public class Class(string name) : Obj(ValueType.Class)
|
||||
{
|
||||
public readonly string Name = name;
|
||||
public Table Members = new();
|
||||
}
|
||||
|
|
@ -1,12 +1,11 @@
|
|||
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 class Context(Function function, ValueType type = ValueType.Context) : Obj(type)
|
||||
{
|
||||
public Function Function = function;
|
||||
public StackRegister<OuterWrapper> PreValues = new();
|
||||
public PushStack<Outer> Outers = new();
|
||||
|
||||
public override string ToString() => "{}";
|
||||
}
|
||||
|
|
@ -9,5 +9,5 @@ public class Function(string name, Segment segment, int outerCount, int argument
|
|||
public readonly int OuterCount = outerCount;
|
||||
public readonly int ArgumentCount = argumentCount;
|
||||
|
||||
public override string ToString() => $"{name}()";
|
||||
public override string ToString() => $"{Name}()";
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
namespace Qrakhen.Qamp.Core.Values.Objects;
|
||||
|
||||
public class Instance(Class @class) : Obj(ValueType.Instance)
|
||||
{
|
||||
public readonly Class Class = @class;
|
||||
public Table Values = new();
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
namespace Qrakhen.Qamp.Core.Values.Objects;
|
||||
|
||||
public class Method(Function function, Value receiver) : Context(function, ValueType.Method)
|
||||
{
|
||||
public Value Receiver = receiver;
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
using Qrakhen.Qamp.Core.Collections;
|
||||
|
||||
namespace Qrakhen.Qamp.Core.Values.Objects;
|
||||
|
||||
using T = ValueType;
|
||||
|
||||
/// <summary>
|
||||
/// Wrapper to access values that are spread around the stack inside contexts
|
||||
/// </summary>
|
||||
public class Outer(Pointer<Value> target) : Obj(T.Outer)
|
||||
{
|
||||
private Value _stored = Value.Void;
|
||||
public Pointer<Value> Target = target;
|
||||
|
||||
public bool IsClosed { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Dynamic getter & setter that returns or writes to either the target value directly,
|
||||
/// or the stored value if this <see cref="Outer"/> has been closed.
|
||||
/// </summary>
|
||||
public Value Value
|
||||
{
|
||||
get => IsClosed ? _stored : Target!.Get();
|
||||
set {
|
||||
if (IsClosed)
|
||||
_stored = value;
|
||||
else
|
||||
Target.Set(value);
|
||||
}
|
||||
}
|
||||
|
||||
public Value Close()
|
||||
{
|
||||
if (IsClosed)
|
||||
throw new Exception($"meh, something not right here (cant close already closed outer)");
|
||||
_stored = Target!.Get();
|
||||
IsClosed = true;
|
||||
return _stored;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
using Qrakhen.Qamp.Core.Collections;
|
||||
|
||||
namespace Qrakhen.Qamp.Core.Values.Objects;
|
||||
|
||||
public class Table : Register<string, Value>
|
||||
{
|
||||
|
||||
}
|
||||
|
|
@ -1,13 +1,14 @@
|
|||
using Qrakhen.Qamp.Core.Values.Objects;
|
||||
using System.Runtime.InteropServices;
|
||||
using Qrakhen.Qamp.Core.Collections;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Qrakhen.Qamp.Core.Values;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Size = sizeof(ulong))]
|
||||
public readonly struct Ptr
|
||||
{
|
||||
private static readonly StackRegister<Obj> _register = new(0x10);
|
||||
private static readonly PushStack<Obj> _register = new(0x10);
|
||||
|
||||
[Serialized] public readonly Address Address;
|
||||
|
||||
|
|
@ -31,5 +32,13 @@ public readonly struct Ptr
|
|||
Address = _register.Add(obj);
|
||||
}
|
||||
|
||||
public T? As<T>(bool throwWhenNull = true) where T : Obj
|
||||
{
|
||||
T? value = Value as T;
|
||||
if (value == null && throwWhenNull)
|
||||
throw new RuntimeException($"Could not convert Object {Value} to target type {typeof(T)}.", Value);
|
||||
return value;
|
||||
}
|
||||
|
||||
public override string ToString() => $"0x{Value}";
|
||||
}
|
||||
|
|
@ -15,6 +15,8 @@ public interface IValue
|
|||
public readonly struct Value : IValue, IDebug<string>
|
||||
{
|
||||
public static readonly Value Void = new Value();
|
||||
public static readonly Value True = new Value(true);
|
||||
public static readonly Value False = new Value(false);
|
||||
|
||||
[FieldOffset(0x00)] [Serialized] public readonly ulong Unsigned;
|
||||
[FieldOffset(0x00)] [Serialized] public readonly long Signed;
|
||||
|
|
@ -29,8 +31,6 @@ public readonly struct Value : IValue, IDebug<string>
|
|||
|
||||
[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) { }
|
||||
|
|
@ -45,6 +45,19 @@ public readonly struct Value : IValue, IDebug<string>
|
|||
|
||||
public dynamic? Dynamic => AsDynamic();
|
||||
|
||||
public T ValueType => Type;
|
||||
|
||||
public bool IsNumber => IsSigned || IsUnsigned || IsDecimal;
|
||||
public bool IsSigned => Is(T.Signed);
|
||||
public bool IsUnsigned => Is(T.Unsigned);
|
||||
public bool IsDecimal => Is(T.Decimal);
|
||||
public bool IsBool => Is(T.Bool);
|
||||
public bool IsString => Is(T.String);
|
||||
public bool IsRef => Is(T.Reference, false);
|
||||
public bool IsObj => Is(T.Object, false);
|
||||
|
||||
public bool IsFalsy => IsBool ? Bool == false : Unsigned == 0;
|
||||
|
||||
public bool Is(T type, bool exact = true)
|
||||
{
|
||||
return exact ? (Type & type) == type : (Type & type) > 0;
|
||||
|
|
@ -52,7 +65,10 @@ public readonly struct Value : IValue, IDebug<string>
|
|||
|
||||
private unsafe dynamic? AsDynamic(bool throwWhenNull = true)
|
||||
{
|
||||
if (Type.HasFlag(T.Object))
|
||||
if (IsString)
|
||||
return Ptr.As<String>()!.Value;
|
||||
|
||||
if (IsObj)
|
||||
return Ptr.Value;
|
||||
|
||||
return Type switch {
|
||||
|
|
@ -88,13 +104,18 @@ public readonly struct Value : IValue, IDebug<string>
|
|||
|
||||
public override int GetHashCode() => Unsigned.GetHashCode();
|
||||
|
||||
public override unsafe string ToString()
|
||||
public override string ToString() => ToString(false);
|
||||
|
||||
public string ToString(bool includeType)
|
||||
{
|
||||
if (!includeType)
|
||||
return $"{AsDynamic(false)}";
|
||||
|
||||
string type = Type.ToString();
|
||||
if (Type.HasFlag(T.Pointer))
|
||||
type = Ptr.Value?.Type.ToString() ?? "NullPtr";
|
||||
|
||||
return $"[{type}, {AsDynamic(false) ?? "null"}]";
|
||||
return $"<{type}, {AsDynamic(false) ?? "null"}>";
|
||||
}
|
||||
|
||||
public string Debug(DebugLevel level = DebugLevel.None)
|
||||
|
|
|
|||
|
|
@ -1,121 +0,0 @@
|
|||
using Qrakhen.Qamp.Core.Logging;
|
||||
|
||||
namespace Qrakhen.Qamp.Core.Values;
|
||||
|
||||
public static class ValueOperation
|
||||
{
|
||||
private static ILogger _logger = LoggerService.Get(nameof(ValueOperation));
|
||||
|
||||
public static Value Not(Value v)
|
||||
{
|
||||
_logger.Method($"{v}");
|
||||
Assert.NotVoid(v);
|
||||
ulong inner = v.Unsigned;
|
||||
return new Value(inner == 0);
|
||||
}
|
||||
|
||||
public static Value Add(Value a, Value b)
|
||||
{
|
||||
_logger.Method($"{a}, {b}");
|
||||
Assert.NotVoid(a, b);
|
||||
Assert.IsPrimitive(a, b);
|
||||
bool convert = a.Type != b.Type; // meh, lets make c# do that for us for now
|
||||
return new Value(a.Dynamic + b.Dynamic);
|
||||
}
|
||||
|
||||
public static Value Subtract(Value a, Value b)
|
||||
{
|
||||
_logger.Method($"{a}, {b}");
|
||||
Assert.NotVoid(a, b);
|
||||
Assert.IsPrimitive(a, b);
|
||||
return new Value(a.Dynamic - b.Dynamic);
|
||||
}
|
||||
|
||||
public static Value Divide(Value a, Value b)
|
||||
{
|
||||
_logger.Method($"{a}, {b}");
|
||||
Assert.NotVoid(a, b);
|
||||
Assert.IsPrimitive(a, b);
|
||||
Assert.NotZero(b);
|
||||
return new Value(a.Dynamic / b.Dynamic);
|
||||
}
|
||||
|
||||
public static Value Modulo(Value a, Value b)
|
||||
{
|
||||
_logger.Method($"{a}, {b}");
|
||||
Assert.NotVoid(a, b);
|
||||
Assert.IsPrimitive(a, b);
|
||||
Assert.NotZero(b);
|
||||
return new Value(a.Dynamic % b.Dynamic);
|
||||
}
|
||||
|
||||
public static Value Multiply(Value a, Value b)
|
||||
{
|
||||
_logger.Method($"{a}, {b}");
|
||||
Assert.NotVoid(a, b);
|
||||
Assert.IsPrimitive(a, b);
|
||||
return new Value(a.Dynamic * b.Dynamic);
|
||||
}
|
||||
|
||||
public static Value Negate(Value a)
|
||||
{
|
||||
_logger.Method($"{a}");
|
||||
Assert.NotVoid(a);
|
||||
Assert.IsPrimitive(a);
|
||||
return new Value(-a.Dynamic);
|
||||
}
|
||||
|
||||
public static Value BitwiseOr(Value a, Value b)
|
||||
{
|
||||
_logger.Method($"{a}, {b}");
|
||||
Assert.NotVoid(a, b);
|
||||
Assert.IsPrimitive(a, b);
|
||||
Assert.IsInteger(a, b);
|
||||
return new Value(a.Dynamic | b.Dynamic);
|
||||
}
|
||||
|
||||
public static Value BitwiseAnd(Value a, Value b)
|
||||
{
|
||||
_logger.Method($"{a}, {b}");
|
||||
Assert.NotVoid(a, b);
|
||||
Assert.IsPrimitive(a, b);
|
||||
Assert.IsInteger(a, b);
|
||||
return new Value(a.Dynamic & b.Dynamic);
|
||||
}
|
||||
|
||||
public static Value BitwiseXor(Value a, Value b)
|
||||
{
|
||||
_logger.Method($"{a}, {b}");
|
||||
Assert.NotVoid(a, b);
|
||||
Assert.IsPrimitive(a, b);
|
||||
Assert.IsInteger(a, b);
|
||||
return new Value(a.Dynamic ^ b.Dynamic);
|
||||
}
|
||||
|
||||
public static Value BitwiseLeft(Value a, Value b)
|
||||
{
|
||||
_logger.Method($"{a}, {b}");
|
||||
Assert.NotVoid(a, b);
|
||||
Assert.IsPrimitive(a, b);
|
||||
Assert.IsInteger(a, b);
|
||||
return new Value(a.Dynamic << (int)(b.Dynamic ?? 0));
|
||||
}
|
||||
|
||||
public static Value BitwiseRight(Value a, Value b)
|
||||
{
|
||||
_logger.Method($"{a}, {b}");
|
||||
Assert.NotVoid(a, b);
|
||||
Assert.IsPrimitive(a, b);
|
||||
Assert.IsInteger(a, b);
|
||||
return new Value(a.Dynamic >> (int)(b.Dynamic ?? 0));
|
||||
}
|
||||
|
||||
public static Value BitwiseInvert(Value a)
|
||||
{
|
||||
_logger.Method($"{a}");
|
||||
Assert.NotVoid(a);
|
||||
Assert.IsPrimitive(a);
|
||||
Assert.IsInteger(a);
|
||||
return new Value(~a.Dynamic);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,9 @@
|
|||
namespace Qrakhen.Qamp.Core.Values;
|
||||
|
||||
/// <summary>
|
||||
/// todo: make value type a byte instead to save some memory on values
|
||||
/// edit: if that's even possible with c#'s memory padding
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum ValueType
|
||||
{
|
||||
|
|
@ -11,6 +15,7 @@ public enum ValueType
|
|||
Decimal = 0x0008,
|
||||
Bool = 0x0010,
|
||||
|
||||
Number = Unsigned | Signed | Decimal,
|
||||
Integer = Unsigned | Signed | Char,
|
||||
Primitive = Integer | Decimal | Bool,
|
||||
|
||||
|
|
@ -29,7 +34,7 @@ public enum ValueType
|
|||
Instance = Object | 0x0020,
|
||||
Class = Object | 0x0100,
|
||||
Method = Class | Function,
|
||||
PreValue = Object | 0x0200,
|
||||
Outer = Object | 0x0200,
|
||||
|
||||
Module = 0xFFFF
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
namespace Qrakhen.Qamp.Digest;
|
||||
|
||||
public class Class1
|
||||
{
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<BaseOutputPath>..\Build\</BaseOutputPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Qrakhen.Qamp.Core\Qrakhen.Qamp.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
namespace Qrakhen.Qamp.Runtime;
|
||||
|
||||
public class Class1
|
||||
{
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||
<BaseOutputPath>..\Build\</BaseOutputPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Qrakhen.Qamp.Core\Qrakhen.Qamp.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -1,12 +1,16 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 18
|
||||
VisualStudioVersion = 18.0.11123.170 d18.0
|
||||
VisualStudioVersion = 18.0.11123.170
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Qrakhen.Qamp.Core", "Qrakhen.Qamp.Core\Qrakhen.Qamp.Core.csproj", "{CA6F4D4E-AF35-4CA6-BC53-C83277EEE46C}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Qrakhen.Qamp.CLI", "Qrakhen.Qamp.CLI\Qrakhen.Qamp.CLI.csproj", "{1D355F76-9D35-4CB2-BF8C-944B0842BC30}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Qrakhen.Qamp.Digest", "Qrakhen.Qamp.Digest\Qrakhen.Qamp.Digest.csproj", "{E836DAE4-9F53-4E3D-BEF7-ADA84BE356CE}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Qrakhen.Qamp.Runtime", "Qrakhen.Qamp.Runtime\Qrakhen.Qamp.Runtime.csproj", "{4B0C0717-6529-4B70-A3EA-C3426F1B3FDC}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
|
@ -21,6 +25,14 @@ Global
|
|||
{1D355F76-9D35-4CB2-BF8C-944B0842BC30}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{1D355F76-9D35-4CB2-BF8C-944B0842BC30}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{1D355F76-9D35-4CB2-BF8C-944B0842BC30}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{E836DAE4-9F53-4E3D-BEF7-ADA84BE356CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{E836DAE4-9F53-4E3D-BEF7-ADA84BE356CE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{E836DAE4-9F53-4E3D-BEF7-ADA84BE356CE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{E836DAE4-9F53-4E3D-BEF7-ADA84BE356CE}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{4B0C0717-6529-4B70-A3EA-C3426F1B3FDC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{4B0C0717-6529-4B70-A3EA-C3426F1B3FDC}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4B0C0717-6529-4B70-A3EA-C3426F1B3FDC}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{4B0C0717-6529-4B70-A3EA-C3426F1B3FDC}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
|
|||
Loading…
Reference in New Issue