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>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
|
<BaseOutputPath>..\Build\</BaseOutputPath>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<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;
|
protected T[] Data;
|
||||||
|
|
||||||
public long Count { get; protected set; }= 0;
|
public long Count { get; protected set; } = 0;
|
||||||
public long Capacity => Data.Length;
|
public long Capacity => Data.Length;
|
||||||
|
|
||||||
protected Expander(int capacity = 0x10)
|
protected Expander(int capacity = 0x10)
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,10 @@
|
||||||
|
|
||||||
namespace Qrakhen.Qamp.Core.Collections;
|
namespace Qrakhen.Qamp.Core.Collections;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Basically just a normal array, but implements <see cref="IGetSet{TKey, TValue}"/> to be used with <see cref="Pointer{T}"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T"></typeparam>
|
||||||
public class FixedArray<T> : IEnumerable<T>, IGetSet<long, T>
|
public class FixedArray<T> : IEnumerable<T>, IGetSet<long, T>
|
||||||
{
|
{
|
||||||
protected T[] Data;
|
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;
|
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;
|
private readonly T[] _data;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,15 @@
|
||||||
namespace Qrakhen.Qamp.Core.Collections;
|
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];
|
Data = new T[initialCapacity];
|
||||||
}
|
}
|
||||||
|
|
@ -15,20 +22,22 @@ public class StackRegister<T> : Expander<T>, IToArray<T>
|
||||||
return default;
|
return default;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Add(T[] array)
|
public void Push(T[] array)
|
||||||
{
|
{
|
||||||
foreach (T value in array) {
|
foreach (T value in array) {
|
||||||
Add(value);
|
Add(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Add(IEnumerable<T> array)
|
public void Push(IEnumerable<T> array)
|
||||||
{
|
{
|
||||||
foreach (T value in array) {
|
foreach (T value in array) {
|
||||||
Add(value);
|
Add(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long Push(T value) => Add(value);
|
||||||
|
|
||||||
public bool TryGet(long index, out T value)
|
public bool TryGet(long index, out T value)
|
||||||
{
|
{
|
||||||
value = default;
|
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> :
|
public class Register<TKey, TValue> :
|
||||||
IGetSet<TKey, TValue>
|
IGetSet<TKey, TValue>,
|
||||||
|
IToArray<TValue>,
|
||||||
|
IEnumerable<KeyValuePair<TKey, TValue>>
|
||||||
where TKey : notnull
|
where TKey : notnull
|
||||||
{
|
{
|
||||||
private readonly Dictionary<TKey, TValue> _data = new();
|
private readonly Dictionary<TKey, TValue> _data = new();
|
||||||
|
|
@ -19,7 +26,21 @@ public class Register<TKey, TValue> :
|
||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool TryGet(TKey key, out TValue? value) => _data.TryGetValue(key, out value);
|
||||||
public TValue Get(TKey key) => _data[key];
|
public TValue Get(TKey key) => _data[key];
|
||||||
public void Set(TKey key, TValue value) => _data[key] = value;
|
public void Set(TKey key, TValue value) => _data[key] = value;
|
||||||
public bool Has(TKey key) => _data.ContainsKey(key);
|
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;
|
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>,
|
public class StackLike<T> : Expander<T>,
|
||||||
IPush<long, T>,
|
IPush<long, T>,
|
||||||
IPop<T>,
|
IPop<T>,
|
||||||
ISeekable<long, T>,
|
ISeekable<long, T>,
|
||||||
IPeekable<T>
|
IPeekable<T>,
|
||||||
|
IToArray<T>
|
||||||
{
|
{
|
||||||
public long Position => Count;
|
public long Position => Count;
|
||||||
|
|
||||||
|
|
@ -39,4 +44,14 @@ public class StackLike<T> : Expander<T>,
|
||||||
|
|
||||||
public bool CanPeek(int delta = 0)
|
public bool CanPeek(int delta = 0)
|
||||||
=> Count + delta > -1 && Count + delta < Count;
|
=> 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 class Builder
|
||||||
{
|
{
|
||||||
public Builder? Outer;
|
public Builder? Outer { get; init; }
|
||||||
public FunctionBuilder Function = new();
|
public FunctionBuilder Function = new();
|
||||||
|
|
||||||
public StackLike<Local> Locals = [];
|
public StackLike<Local> Locals = [];
|
||||||
public StackLike<Outer> Outers = [];
|
public StackLike<Outer> Outers = [];
|
||||||
public int ScopeDepth = 0;
|
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;
|
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 PushStack<Instruction> Instructions = new();
|
||||||
public readonly StackRegister<Value> Constants = new();
|
public readonly PushStack<Value> Constants = new();
|
||||||
|
|
||||||
public Segment Build()
|
public Segment Build()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,11 @@ public class DigesterProvider
|
||||||
public object ExpressionDigester; // etc.
|
public object ExpressionDigester; // etc.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public abstract class xDigester
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public class Digester : ISteppable<Token>
|
public class Digester : ISteppable<Token>
|
||||||
{
|
{
|
||||||
private readonly CompilerState _compiler;
|
private readonly CompilerState _compiler;
|
||||||
|
|
@ -105,7 +110,7 @@ public class Digester : ISteppable<Token>
|
||||||
{
|
{
|
||||||
Emit(Op.Return);
|
Emit(Op.Return);
|
||||||
Function function = Builder.Function.Build();
|
Function function = Builder.Function.Build();
|
||||||
Builder = Builder.Outer!;
|
Builder = Builder.Outer;
|
||||||
return function;
|
return function;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -129,15 +134,19 @@ public class Digester : ISteppable<Token>
|
||||||
else if (Match(T.Return)) Return();
|
else if (Match(T.Return)) Return();
|
||||||
else if (Match(T.TypeOf)) TypeOf();
|
else if (Match(T.TypeOf)) TypeOf();
|
||||||
else if (Match(T.Print)) Print();
|
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.Export)) Export();
|
||||||
else if (Match(T.Import)) Import();
|
else if (Match(T.Import)) Import();
|
||||||
else if (Match(T.ContextOpen)) Context();
|
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");
|
Consume(T.Semicolon, "Expected ';' after statement");
|
||||||
Emit(Op.Pop); // in an expression statement, we definitely do not want to remain the value on stack,
|
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.
|
// 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
|
// 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()
|
internal void Context()
|
||||||
{
|
{
|
||||||
|
|
@ -221,7 +230,7 @@ public class Digester : ISteppable<Token>
|
||||||
ExpressionParser.Variable(this, name, false);
|
ExpressionParser.Variable(this, name, false);
|
||||||
Consume(T.ContextOpen, "Expected '{' for class body declaration.", false);
|
Consume(T.ContextOpen, "Expected '{' for class body declaration.", false);
|
||||||
while (!Check(T.ContextClose) && !Check(T.Eof))
|
while (!Check(T.ContextClose) && !Check(T.Eof))
|
||||||
DeclareMethod();
|
DeclareMember();
|
||||||
|
|
||||||
Consume(T.ContextClose, "Expected '}' to end class body declaration.", false);
|
Consume(T.ContextClose, "Expected '}' to end class body declaration.", false);
|
||||||
Emit(Op.Pop);
|
Emit(Op.Pop);
|
||||||
|
|
@ -241,8 +250,9 @@ public class Digester : ISteppable<Token>
|
||||||
DefineVariable(global);
|
DefineVariable(global);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void DeclareMethod()
|
internal void DeclareMember()
|
||||||
{
|
{
|
||||||
|
// todo: only methods allowed atm, add fields and shit too
|
||||||
_logger.Method();
|
_logger.Method();
|
||||||
Consume(T.Identifier, "Expected method name.", false);
|
Consume(T.Identifier, "Expected method name.", false);
|
||||||
long identifier = IdentifierConstant(Previous.Value!);
|
long identifier = IdentifierConstant(Previous.Value!);
|
||||||
|
|
@ -258,7 +268,7 @@ public class Digester : ISteppable<Token>
|
||||||
_logger.Method();
|
_logger.Method();
|
||||||
long global = ParseVariable();
|
long global = ParseVariable();
|
||||||
if (Match(T.Equal))
|
if (Match(T.Equal))
|
||||||
Expression();
|
ParseExpression();
|
||||||
else
|
else
|
||||||
Emit(Op.Null);
|
Emit(Op.Null);
|
||||||
Consume(T.Semicolon, "missing ; after variable declaration");
|
Consume(T.Semicolon, "missing ; after variable declaration");
|
||||||
|
|
@ -279,7 +289,7 @@ public class Digester : ISteppable<Token>
|
||||||
{
|
{
|
||||||
_logger.Method();
|
_logger.Method();
|
||||||
Consume(T.GroupOpen, "Expected '(' after if");
|
Consume(T.GroupOpen, "Expected '(' after if");
|
||||||
Expression();
|
ParseExpression();
|
||||||
Consume(T.GroupClose, "Expected ')' after condition");
|
Consume(T.GroupClose, "Expected ')' after condition");
|
||||||
|
|
||||||
long then = EmitJump(Op.JumpIfFalse);
|
long then = EmitJump(Op.JumpIfFalse);
|
||||||
|
|
@ -308,7 +318,7 @@ public class Digester : ISteppable<Token>
|
||||||
_logger.Method();
|
_logger.Method();
|
||||||
long start = Function.CurrentInstruction;
|
long start = Function.CurrentInstruction;
|
||||||
Consume(T.GroupOpen, "Expected while condition to be placed inside parentheses. Missing '('.", false);
|
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);
|
Consume(T.GroupClose, "Expected while condition to be placed inside parentheses. Missing ')'.", false);
|
||||||
long exit = EmitJump(Op.JumpIfFalse);
|
long exit = EmitJump(Op.JumpIfFalse);
|
||||||
Emit(Op.Pop);
|
Emit(Op.Pop);
|
||||||
|
|
@ -335,13 +345,13 @@ public class Digester : ISteppable<Token>
|
||||||
} else if (Match(T.Var)) {
|
} else if (Match(T.Var)) {
|
||||||
DeclareVariable();
|
DeclareVariable();
|
||||||
} else {
|
} else {
|
||||||
StatementExpression();
|
Expression();
|
||||||
}
|
}
|
||||||
|
|
||||||
long start = Function.CurrentInstruction;
|
long start = Function.CurrentInstruction;
|
||||||
long exit = -1;
|
long exit = -1;
|
||||||
if (!Match(T.Semicolon)) {
|
if (!Match(T.Semicolon)) {
|
||||||
Expression();
|
ParseExpression();
|
||||||
Consume(T.Semicolon, "Expected ';' after condition");
|
Consume(T.Semicolon, "Expected ';' after condition");
|
||||||
exit = EmitJump(Op.JumpIfFalse);
|
exit = EmitJump(Op.JumpIfFalse);
|
||||||
Emit(Op.Pop);
|
Emit(Op.Pop);
|
||||||
|
|
@ -350,7 +360,7 @@ public class Digester : ISteppable<Token>
|
||||||
if (!Match(T.GroupClose)) {
|
if (!Match(T.GroupClose)) {
|
||||||
long body = EmitJump(Op.Jump);
|
long body = EmitJump(Op.Jump);
|
||||||
long increment = Function.Segment.Instructions.Count;
|
long increment = Function.Segment.Instructions.Count;
|
||||||
Expression();
|
ParseExpression();
|
||||||
Emit(Op.Pop);
|
Emit(Op.Pop);
|
||||||
Consume(T.GroupClose, "Expected ')' after for clause");
|
Consume(T.GroupClose, "Expected ')' after for clause");
|
||||||
|
|
||||||
|
|
@ -378,15 +388,36 @@ public class Digester : ISteppable<Token>
|
||||||
internal void Print()
|
internal void Print()
|
||||||
{
|
{
|
||||||
_logger.Method();
|
_logger.Method();
|
||||||
Expression();
|
ParseExpression();
|
||||||
Consume(T.Semicolon, "Expected ';' after print call.");
|
Consume(T.Semicolon, "Expected ';' after print call.");
|
||||||
Emit(Op.Print);
|
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()
|
internal void TypeOf()
|
||||||
{
|
{
|
||||||
_logger.Method();
|
_logger.Method();
|
||||||
Expression();
|
ParseExpression();
|
||||||
Emit(Op.Typeof);
|
Emit(Op.Typeof);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -403,9 +434,9 @@ public class Digester : ISteppable<Token>
|
||||||
while (Builder.Locals.Count > 0 &&
|
while (Builder.Locals.Count > 0 &&
|
||||||
Builder.Locals.Peek().Depth > Builder.ScopeDepth) {
|
Builder.Locals.Peek().Depth > Builder.ScopeDepth) {
|
||||||
if (Builder.Locals.Peek().IsCaptured) {
|
if (Builder.Locals.Peek().IsCaptured) {
|
||||||
Emit(OpCode.CloseUpvalue);
|
Emit(Op.CloseOuter);
|
||||||
} else {
|
} else {
|
||||||
Emit(OpCode.Pop);
|
Emit(Op.Pop);
|
||||||
}
|
}
|
||||||
Builder.Locals.Pop();
|
Builder.Locals.Pop();
|
||||||
}
|
}
|
||||||
|
|
@ -425,17 +456,16 @@ public class Digester : ISteppable<Token>
|
||||||
DefineVariable(ParseVariable());
|
DefineVariable(ParseVariable());
|
||||||
} while (Match(T.Comma));
|
} while (Match(T.Comma));
|
||||||
}
|
}
|
||||||
|
|
||||||
Consume(T.GroupClose, "Expected ')' after argument list.");
|
Consume(T.GroupClose, "Expected ')' after argument list.");
|
||||||
Consume(T.ContextOpen, "Expected function body");
|
Consume(T.ContextOpen, "Expected function body");
|
||||||
Block();
|
Block();
|
||||||
Function function = FinishBuilder();
|
Function function = FinishBuilder();
|
||||||
Emit(Op.Closure);
|
Emit(Op.Context);
|
||||||
Emit(MakeConstant(Obj.Create(function)));
|
EmitDynamic(MakeConstant(Obj.Create(function)));
|
||||||
for (int i = 0; i < function.OuterCount; i++) {
|
for (int i = 0; i < function.OuterCount; i++) {
|
||||||
Outer outer = next.Outers.Get(i);
|
Outer outer = next.Outers.Get(i);
|
||||||
Emit(outer.IsLocal ? 1 : 0);
|
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;
|
byte count = 0;
|
||||||
if (!Check(T.GroupClose)) {
|
if (!Check(T.GroupClose)) {
|
||||||
do {
|
do {
|
||||||
Expression();
|
ParseExpression();
|
||||||
if (count++ >= 0xFF)
|
if (count++ >= 0xFF)
|
||||||
ErrorAtCurrent("How many arguments do you need?");
|
ErrorAtCurrent("How many arguments do you need?");
|
||||||
} while (Match(T.Comma));
|
} while (Match(T.Comma));
|
||||||
|
|
@ -621,7 +651,8 @@ public class Digester : ISteppable<Token>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal void EmitDynamic(byte[] data)
|
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);
|
Emit(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -52,8 +52,8 @@ public static class ExpressionParser
|
||||||
|
|
||||||
static void Call(Digester digester, bool canAssign)
|
static void Call(Digester digester, bool canAssign)
|
||||||
{
|
{
|
||||||
byte count = digester.ArgumentList();
|
byte count = digester.Arguments();
|
||||||
digester.Emit(OpCode.Call, count);
|
digester.Emit(OpCode.Invoke, count);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void Dot(Digester digester, bool canAssign)
|
static void Dot(Digester digester, bool canAssign)
|
||||||
|
|
@ -62,14 +62,14 @@ public static class ExpressionParser
|
||||||
long name = digester.IdentifierConstant(digester.Previous.Value);
|
long name = digester.IdentifierConstant(digester.Previous.Value);
|
||||||
|
|
||||||
if (canAssign && digester.Match(TokenType.Equal)) {
|
if (canAssign && digester.Match(TokenType.Equal)) {
|
||||||
digester.Expression();
|
digester.ParseExpression();
|
||||||
digester.EmitDynamic(OpCode.SetProperty, name);
|
digester.EmitDynamic(OpCode.SetMember, name);
|
||||||
} else if (digester.Match(TokenType.GroupOpen)) {
|
} else if (digester.Match(TokenType.GroupOpen)) {
|
||||||
Byte argCount = 0; //argumentList();
|
byte argCount = digester.Arguments();
|
||||||
digester.Emit(OpCode.Invoke, name.GetBytes());
|
digester.EmitDynamic(OpCode.InvokeMember, name);
|
||||||
digester.EmitDynamic(argCount);
|
digester.Emit(argCount);
|
||||||
} else {
|
} 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)
|
static void Group(Digester digester, bool canAssign)
|
||||||
{
|
{
|
||||||
digester.Expression();
|
digester.ParseExpression();
|
||||||
digester.Consume(TokenType.GroupClose, "Expected ')' after expression.");
|
digester.Consume(TokenType.GroupClose, "Expected ')' after expression.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -100,7 +100,7 @@ public static class ExpressionParser
|
||||||
{
|
{
|
||||||
int length = 0;
|
int length = 0;
|
||||||
while (!digester.Check(TokenType.ArrayClose)) {
|
while (!digester.Check(TokenType.ArrayClose)) {
|
||||||
digester.Expression();
|
digester.ParseExpression();
|
||||||
length++;
|
length++;
|
||||||
if (digester.Check(TokenType.Comma))
|
if (digester.Check(TokenType.Comma))
|
||||||
digester.Next();
|
digester.Next();
|
||||||
|
|
@ -113,19 +113,19 @@ public static class ExpressionParser
|
||||||
|
|
||||||
static void ArrayAdd(Digester digester, bool canAssign)
|
static void ArrayAdd(Digester digester, bool canAssign)
|
||||||
{
|
{
|
||||||
digester.Expression();
|
digester.ParseExpression();
|
||||||
digester.Emit(OpCode.ArrayAdd);
|
digester.Emit(OpCode.ArrayAdd);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void Index(Digester digester, bool canAssign)
|
static void Index(Digester digester, bool canAssign)
|
||||||
{
|
{
|
||||||
digester.Expression();
|
digester.ParseExpression();
|
||||||
digester.Consume(TokenType.ArrayClose, "expected ] after array accessor");
|
digester.Consume(TokenType.ArrayClose, "expected ] after array accessor");
|
||||||
if (canAssign && digester.Match(TokenType.ArrayAdd)) {
|
if (canAssign && digester.Match(TokenType.ArrayAdd)) {
|
||||||
digester.Expression();
|
digester.ParseExpression();
|
||||||
digester.Emit(OpCode.ArrayAdd);
|
digester.Emit(OpCode.ArrayAdd);
|
||||||
} else if (canAssign && digester.Match(TokenType.Equal)) {
|
} else if (canAssign && digester.Match(TokenType.Equal)) {
|
||||||
digester.Expression();
|
digester.ParseExpression();
|
||||||
digester.Emit(OpCode.ArraySet);
|
digester.Emit(OpCode.ArraySet);
|
||||||
} else {
|
} else {
|
||||||
digester.Emit(OpCode.ArrayGet);
|
digester.Emit(OpCode.ArrayGet);
|
||||||
|
|
@ -183,7 +183,7 @@ public static class ExpressionParser
|
||||||
}
|
}
|
||||||
|
|
||||||
if (canAssign && digester.Match(TokenType.Equal)) {
|
if (canAssign && digester.Match(TokenType.Equal)) {
|
||||||
digester.Expression();
|
digester.ParseExpression();
|
||||||
digester.Emit(Set);
|
digester.Emit(Set);
|
||||||
digester.EmitDynamic(variable);
|
digester.EmitDynamic(variable);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -211,9 +211,9 @@ public static class ExpressionParser
|
||||||
Variable(digester, "this", false);
|
Variable(digester, "this", false);
|
||||||
if (digester.Match(TokenType.GroupOpen))
|
if (digester.Match(TokenType.GroupOpen))
|
||||||
{
|
{
|
||||||
byte arguments = digester.ArgumentList();
|
byte arguments = digester.Arguments();
|
||||||
Variable(digester, "base", false);
|
Variable(digester, "base", false);
|
||||||
digester.Emit(OpCode.BaseCall);
|
digester.Emit(OpCode.InvokeBase);
|
||||||
digester.Emit(arguments);
|
digester.Emit(arguments);
|
||||||
} else {
|
} else {
|
||||||
Variable(digester, "base", false);
|
Variable(digester, "base", false);
|
||||||
|
|
@ -294,7 +294,7 @@ public static class ExpressionParser
|
||||||
_rules[TokenType.Export] = 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.Import] = new Rule(null, null, Weight.None);
|
||||||
_rules[TokenType.Return] = 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.This] = new Rule(This, null, Weight.None);
|
||||||
_rules[TokenType.True] = new Rule(Literal, null, Weight.None);
|
_rules[TokenType.True] = new Rule(Literal, null, Weight.None);
|
||||||
_rules[TokenType.Var] = new Rule(null, 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.Tokenization;
|
||||||
using Qrakhen.Qamp.Core.Values;
|
using Qrakhen.Qamp.Core.Values;
|
||||||
|
|
||||||
using V = Qrakhen.Qamp.Core.Values.ValueType;
|
|
||||||
|
|
||||||
namespace Qrakhen.Qamp.Core;
|
namespace Qrakhen.Qamp.Core;
|
||||||
|
|
||||||
public class QampException(string message, TokenPosition position = default)
|
public class QampException(string message, object? context = null)
|
||||||
: Exception(message)
|
: Exception(message)
|
||||||
{
|
{
|
||||||
public readonly TokenPosition Position = position;
|
public readonly object? Context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ReaderException(string message, IReader<Token> reader)
|
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 readonly Token Token = token;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ExecutionException(string message, TokenPosition position = default)
|
public class RuntimeException(string message, object? context = null)
|
||||||
: QampException(message, position);
|
: QampException(message, context);
|
||||||
|
|
||||||
public class DivisionByZeroException(string? message, TokenPosition position = default)
|
public class DivisionByZeroException(string? message, object? context = null)
|
||||||
: ExecutionException(message ?? "Can not divide by zero", position);
|
: RuntimeException(message ?? "Can not divide by zero", context);
|
||||||
|
|
||||||
public class ConversionException(string message, Value value, TokenPosition position = default)
|
public class ConversionException(string message, Value value, object? context = null)
|
||||||
: ExecutionException(message, position)
|
: RuntimeException(message, context)
|
||||||
{
|
{
|
||||||
public readonly Value Value = value;
|
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
|
public enum OpCode
|
||||||
{
|
{
|
||||||
None = 0,
|
F_Mask = 0xF0,
|
||||||
Constant,
|
|
||||||
Null,
|
None = 0x00,
|
||||||
True,
|
Constant = 0x01,
|
||||||
False,
|
Null = 0x02,
|
||||||
Pop,
|
Pop = 0x03,
|
||||||
Export,
|
Cast = 0x04,
|
||||||
Import,
|
|
||||||
GetLocal,
|
False = 0x0e,
|
||||||
SetLocal,
|
True = 0x0f,
|
||||||
GetGlobal,
|
|
||||||
DefineGlobal,
|
Val = 0x10,
|
||||||
SetGlobal,
|
Ref = 0x11,
|
||||||
GetOuter,
|
|
||||||
SetOuter,
|
SetGlobal = 0x20,
|
||||||
GetProperty,
|
GetGlobal = 0x21,
|
||||||
SetProperty,
|
GetLocal = 0x22,
|
||||||
Property,
|
SetLocal = 0x23,
|
||||||
Base,
|
GetOuter = 0x24,
|
||||||
Equal,
|
SetOuter = 0x25,
|
||||||
Greater,
|
GetMember = 0x26,
|
||||||
Less,
|
SetMember = 0x27,
|
||||||
Add,
|
|
||||||
Subtract,
|
DefineGlobal = 0x30,
|
||||||
Multiply,
|
CloseOuter = 0x31,
|
||||||
Divide,
|
|
||||||
Modulo,
|
AssignValue = 0x40, // unsure
|
||||||
BitwiseAnd,
|
AssignReference = 0x41, // unsure
|
||||||
BitwiseOr,
|
|
||||||
BitwiseXor,
|
F_Operation = 0x60,
|
||||||
BitwiseNot,
|
Add = 0x60,
|
||||||
BitwiseLeft,
|
Subtract = 0x61,
|
||||||
BitwiseRight,
|
Multiply = 0x62,
|
||||||
Increment,
|
Divide = 0x63,
|
||||||
Decrement,
|
Modulo = 0x64,
|
||||||
Not,
|
BitwiseAnd = 0x65,
|
||||||
Negate,
|
BitwiseOr = 0x66,
|
||||||
Ref,
|
BitwiseXor = 0x67,
|
||||||
Array,
|
BitwiseLeft = 0x68,
|
||||||
ArrayGet,
|
BitwiseRight = 0x69,
|
||||||
ArraySet,
|
F_Unary = 0x0a,
|
||||||
ArrayAdd,
|
Not = 0x6a,
|
||||||
ArrayRemove,
|
Negate = 0x6b,
|
||||||
Print,
|
BitwiseNot = 0x6c,
|
||||||
PrintExpr,
|
|
||||||
Typeof,
|
F_Compare = 0x50,
|
||||||
Jump,
|
Equal = 0x50,
|
||||||
JumpIfFalse,
|
Greater = 0x51,
|
||||||
Loop,
|
Less = 0x52,
|
||||||
Call,
|
|
||||||
Invoke,
|
Increment = 0x70,
|
||||||
BaseCall,
|
Decrement = 0x71,
|
||||||
Closure,
|
|
||||||
CloseUpvalue,
|
Array = 0xc0,
|
||||||
Return,
|
ArrayGet = 0xc1,
|
||||||
Class,
|
ArraySet = 0xc2,
|
||||||
Inherit,
|
ArrayAdd = 0xc3,
|
||||||
Method
|
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.Collections;
|
||||||
using Qrakhen.Qamp.Core.Compilation;
|
|
||||||
using Qrakhen.Qamp.Core.Logging;
|
using Qrakhen.Qamp.Core.Logging;
|
||||||
using Qrakhen.Qamp.Core.Tokenization;
|
using Qrakhen.Qamp.Core.Tokenization;
|
||||||
using Qrakhen.Qamp.Core.Values;
|
using Qrakhen.Qamp.Core.Values;
|
||||||
using Qrakhen.Qamp.Core.Values.Objects;
|
using Qrakhen.Qamp.Core.Values.Objects;
|
||||||
|
using System.Xml.Linq;
|
||||||
using String = Qrakhen.Qamp.Core.Values.Objects.String;
|
using String = Qrakhen.Qamp.Core.Values.Objects.String;
|
||||||
|
|
||||||
namespace Qrakhen.Qamp.Core.Execution;
|
namespace Qrakhen.Qamp.Core.Execution;
|
||||||
|
|
||||||
using Op = OpCode;
|
using Op = OpCode;
|
||||||
|
using T = Values.ValueType;
|
||||||
/*
|
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Call
|
public class Call
|
||||||
{
|
{
|
||||||
public Closure Closure;
|
public Context Context;
|
||||||
public InstructionPtr Instruction;
|
public InstructionPtr Instruction;
|
||||||
public Pointer<Value> StackPtr;
|
public Pointer<Value> StackPtr;
|
||||||
|
|
||||||
public Call(Closure closure, StackLike<Value> stack, long stackOffset)
|
public Call(Context context, StackLike<Value> stack, long stackOffset)
|
||||||
{
|
{
|
||||||
Closure = closure;
|
Context = context;
|
||||||
Instruction = new InstructionPtr(Closure.Function.Segment);
|
Instruction = new InstructionPtr(Context.Function.Segment);
|
||||||
StackPtr = new Pointer<Value>(stack, stackOffset);
|
StackPtr = new Pointer<Value>(stack, stackOffset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public unsafe class OuterWrapper
|
|
||||||
{
|
|
||||||
public Value* Target;
|
|
||||||
public Value Stored;
|
|
||||||
}
|
|
||||||
|
|
||||||
public class Runner : IDisposable
|
public class Runner : IDisposable
|
||||||
{
|
{
|
||||||
private readonly ILogger _logger = LoggerService.Get<Runner>();
|
private readonly ILogger _logger = LoggerService.Get<Runner>();
|
||||||
|
|
||||||
public readonly Options Options;
|
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... ?
|
// todo: maybe have a pointer everywhere to stacks or arrays and use those rather than built-in pointers or cursors... ?
|
||||||
public StackLike<Value> Stack;
|
private StackLike<Value> Stack;
|
||||||
public long Cursor;
|
private long Cursor => Stack.Position;
|
||||||
|
|
||||||
public Register<string, Value> Globals = new Register<string, Value>();
|
private List<Outer> Outers = [];
|
||||||
public Register<string, Value> Imports = new Register<string, Value>();
|
|
||||||
public Register<string, Value> Exports = new Register<string, Value>();
|
private Register<string, Value> Globals = new();
|
||||||
|
private Register<string, Value> Imports = new();
|
||||||
|
private Register<string, Value> Exports = new();
|
||||||
|
|
||||||
public Runner(Options options)
|
public Runner(Options options)
|
||||||
{
|
{
|
||||||
|
|
@ -172,39 +54,74 @@ public class Runner : IDisposable
|
||||||
{
|
{
|
||||||
_logger.Method();
|
_logger.Method();
|
||||||
using Reader reader = new Reader(stream);
|
using Reader reader = new Reader(stream);
|
||||||
Digester digester = new Digester(reader);
|
Compilation.Digester digester = new(reader);
|
||||||
Function function = digester.Digest();
|
Function function = digester.Digest();
|
||||||
File.WriteAllBytes("func.sqi", function.Segment.Serialize());
|
File.WriteAllBytes("func.sqi", function.Segment.Serialize());
|
||||||
Closure closure = new Closure(function);
|
Context closure = new Context(function);
|
||||||
Push(Obj.Create(closure));
|
Push(Obj.Create(closure));
|
||||||
Call(closure, 0);
|
Invoke(closure, 0);
|
||||||
return Interpret();
|
return Execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
private ExecutionResult Interpret()
|
private bool TestFlag(Op code, Op flag) => (code & Op.F_Mask) == flag;
|
||||||
|
|
||||||
|
private ExecutionResult Execute()
|
||||||
{
|
{
|
||||||
_logger.Method();
|
_logger.Method();
|
||||||
Call call = Calls.Peek(-1);
|
Call call = Calls.Peek(-1);
|
||||||
do {
|
do {
|
||||||
Op opCode = call.Instruction.Next();
|
Op opCode = call.Instruction.Next();
|
||||||
_logger.Verbose($"OpCode: {opCode}");
|
_logger.Verbose($"OpCode: {opCode}");
|
||||||
|
|
||||||
|
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) {
|
switch (opCode) {
|
||||||
case Op.Constant: Push(call.Instruction.NextConstant()); break;
|
case Op.Constant: Push(call.Instruction.NextConstant()); break;
|
||||||
case Op.Pop: Pop(); break;
|
case Op.Pop: Pop(); break;
|
||||||
|
case Op.True: Push(Value.True); break;
|
||||||
|
case Op.False: Push(Value.False); break;
|
||||||
|
|
||||||
case Op.Not: OpNot(); break;
|
case Op.GetGlobal: {
|
||||||
case Op.Add: OpAdd(); break;
|
string? name = call.Instruction.GetStringConstant(call.Instruction.NextDynamic())?.Value;
|
||||||
case Op.Subtract: OpSubtract(); break;
|
if (string.IsNullOrEmpty(name))
|
||||||
case Op.Divide: OpDivide(); break;
|
throw new RuntimeException($"tried to set global variable with empty name");
|
||||||
case Op.Modulo: OpModulo(); break;
|
if (!Globals.Has(name))
|
||||||
case Op.Multiply: OpMultiply(); break;
|
Push(Value.Void);
|
||||||
case Op.Negate: OpNegate(); break;
|
else
|
||||||
case Op.BitwiseOr: OpBitwiseOr(); break;
|
Push(Globals[name]);
|
||||||
case Op.BitwiseAnd: OpBitwiseAnd(); break;
|
_logger.Verbose($"got global {name} ({Peek()})");
|
||||||
case Op.BitwiseXor: OpBitwiseXor(); break;
|
break;
|
||||||
case Op.BitwiseNot: OpBitwiseNot(); break;
|
}
|
||||||
case Op.BitwiseLeft: OpBitwiseLeft(); break;
|
|
||||||
case Op.BitwiseRight: OpBitwiseRight(); 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: {
|
case Op.GetLocal: {
|
||||||
long slot = call.Instruction.NextDynamic();
|
long slot = call.Instruction.NextDynamic();
|
||||||
|
|
@ -220,38 +137,158 @@ public class Runner : IDisposable
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case Op.DefineGlobal: {
|
case Op.GetOuter: {
|
||||||
string? name = call.Instruction.GetStringConstant(call.Instruction.NextDynamic())?.Value;
|
long slot = call.Instruction.NextDynamic();
|
||||||
if (string.IsNullOrEmpty(name))
|
Push(call.Context.Outers[slot].Value);
|
||||||
throw new ExecutionException($"tried to define global variable with empty name");
|
|
||||||
Globals[name] = Pop();
|
|
||||||
_logger.Verbose($"defined global {name} as {Globals[name]}");
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case Op.SetGlobal: {
|
case Op.SetOuter: {
|
||||||
string? name = call.Instruction.GetStringConstant(call.Instruction.NextDynamic())?.Value;
|
long slot = call.Instruction.NextDynamic();
|
||||||
if (string.IsNullOrEmpty(name))
|
call.Context.Outers[slot].Value = Peek();
|
||||||
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;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case Op.GetGlobal: {
|
case Op.GetMember: {
|
||||||
string? name = call.Instruction.GetStringConstant(call.Instruction.NextDynamic())?.Value;
|
Value value = Peek();
|
||||||
if (string.IsNullOrEmpty(name))
|
if (!value.Is(T.Instance))
|
||||||
throw new ExecutionException($"tried to set global variable with empty name");
|
return Error("Only instances have members.");
|
||||||
if (!Globals.Has(name))
|
Instance instance = value.Ptr.As<Instance>()!;
|
||||||
Push(Value.Void);
|
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
|
else
|
||||||
Push(Globals[name]);
|
context.Outers.Add(call.Context.Outers[index]);
|
||||||
_logger.Verbose($"got global {name} ({Peek()})");
|
}
|
||||||
break;
|
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: {
|
case Op.Typeof: {
|
||||||
Value value = Pop();
|
Value value = Pop();
|
||||||
|
|
@ -265,25 +302,57 @@ public class Runner : IDisposable
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case Op.Return: // todo: not done
|
case Op.PrintStack: {
|
||||||
Value result = Pop();
|
for (int i = 0; i < Cursor; i++) {
|
||||||
Calls.Pop();
|
Console.WriteLine($"{Stack[i].ToString(true)}");
|
||||||
if (Calls.Count == 0) {
|
|
||||||
Pop();
|
|
||||||
return ExecutionResult.OK;
|
|
||||||
}
|
}
|
||||||
Cursor = call.StackPtr.Ptr; // well find a better way...
|
|
||||||
Push(result);
|
|
||||||
call = Calls.Peek();
|
|
||||||
break;
|
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);
|
} while (call.Instruction.Segment.Instructions.Length > call.Instruction.Ptr);
|
||||||
|
|
||||||
return ExecutionResult.OK;
|
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();
|
_logger.Method();
|
||||||
if (argumentCount != closure.Function.ArgumentCount)
|
if (argumentCount != closure.Function.ArgumentCount)
|
||||||
|
|
@ -297,6 +366,90 @@ public class Runner : IDisposable
|
||||||
return true;
|
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)
|
private Value Peek(int delta = -1)
|
||||||
{
|
{
|
||||||
_logger.Method();
|
_logger.Method();
|
||||||
|
|
@ -315,92 +468,23 @@ public class Runner : IDisposable
|
||||||
Stack.Push(value);
|
Stack.Push(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OpNot()
|
private Operation PopOperation(Op code)
|
||||||
{
|
|
||||||
Value value = Pop();
|
|
||||||
Push(ValueOperation.Not(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OpAdd()
|
|
||||||
{
|
{
|
||||||
|
_logger.Method(code);
|
||||||
|
if ((code & Op.F_Unary) == Op.F_Unary)
|
||||||
|
return new Operation(code, Pop(), default);
|
||||||
Value right = Pop();
|
Value right = Pop();
|
||||||
Value left = 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();
|
_logger.Method(message);
|
||||||
Value left = Pop();
|
_logger.Error(message, context);
|
||||||
Push(ValueOperation.Subtract(left, right));
|
if (@throw)
|
||||||
}
|
throw new QampException(message, context);
|
||||||
|
return ExecutionResult.Execution;
|
||||||
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()
|
public void Dispose()
|
||||||
|
|
@ -409,6 +493,11 @@ public class Runner : IDisposable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class GarbageCollector
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public enum ExecutionResult
|
public enum ExecutionResult
|
||||||
{
|
{
|
||||||
OK = 0x0000,
|
OK = 0x0000,
|
||||||
|
|
|
||||||
|
|
@ -29,18 +29,27 @@ public class Segment(
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||||
/// <param name="offset"></param>
|
/// <remarks>
|
||||||
/// <returns></returns>
|
/// All of this is done to compress compiled instruction size.
|
||||||
public long ReadDynamic(long offset, out byte length)
|
/// 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 value = Read(offset);
|
||||||
byte[] bytes = new byte[8];
|
read = 1;
|
||||||
for (int i = 0; i < length; i++) {
|
if (value < 0x80) // 0x80 flag for length, less than 0x80 means direct read
|
||||||
bytes[i] = Instructions[offset + 1 + i];
|
return value;
|
||||||
}
|
int length = value ^ 0x80;
|
||||||
return bytes.ToInt64();
|
read += length;
|
||||||
|
var data = new byte[8];
|
||||||
|
for (int i = 0; i < length; i++)
|
||||||
|
data[i] = Read(offset + i);
|
||||||
|
return data.ToInt64();
|
||||||
}
|
}
|
||||||
|
|
||||||
public short ReadShort(long offset) => BitConverter.ToInt16(Read(offset, 2));
|
public 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>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
<BaseOutputPath>..\Build\</BaseOutputPath>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
|
|
@ -88,7 +88,7 @@ public class DefaultDialect : Dialect
|
||||||
Define(Ref, "ref");
|
Define(Ref, "ref");
|
||||||
Define(Function, "function");
|
Define(Function, "function");
|
||||||
Define(Class, "class");
|
Define(Class, "class");
|
||||||
Define(Super, "base");
|
Define(Base, "base");
|
||||||
Define(TypeOf, "typeof");
|
Define(TypeOf, "typeof");
|
||||||
Define(Print, "print");
|
Define(Print, "print");
|
||||||
Define(Import, "import");
|
Define(Import, "import");
|
||||||
|
|
@ -109,7 +109,7 @@ public class ClassicDialect : DefaultDialect
|
||||||
Define(Return, "<:");
|
Define(Return, "<:");
|
||||||
Define(Ref, "*&");
|
Define(Ref, "*&");
|
||||||
Define(Function, "fq");
|
Define(Function, "fq");
|
||||||
Define(Super, "^~");
|
Define(Base, "^~");
|
||||||
Define(TypeOf, "?:");
|
Define(TypeOf, "?:");
|
||||||
Define(Print, "::");
|
Define(Print, "::");
|
||||||
Define(Import, "<!");
|
Define(Import, "<!");
|
||||||
|
|
|
||||||
|
|
@ -32,13 +32,19 @@ internal static partial class ReaderPatterns
|
||||||
Keywords["var"] = Var;
|
Keywords["var"] = Var;
|
||||||
Keywords["while"] = While;
|
Keywords["while"] = While;
|
||||||
Keywords["do"] = Do;
|
Keywords["do"] = Do;
|
||||||
Keywords["typeof"] = TypeOf;
|
|
||||||
Keywords["ref"] = Ref;
|
Keywords["ref"] = Ref;
|
||||||
Keywords["function"] = Function;
|
Keywords["function"] = Function;
|
||||||
|
Keywords["funqtion"] = Function;
|
||||||
|
Keywords["fq"] = Function;
|
||||||
|
Keywords["funq"] = Function;
|
||||||
Keywords["return"] = Return;
|
Keywords["return"] = Return;
|
||||||
Keywords["class"] = Class;
|
Keywords["class"] = Class;
|
||||||
Keywords["super"] = Super;
|
Keywords["base"] = Base;
|
||||||
|
Keywords["typeof"] = TypeOf;
|
||||||
Keywords["print"] = Print;
|
Keywords["print"] = Print;
|
||||||
|
Keywords["globals"] = PrintGlobals;
|
||||||
|
Keywords["stack"] = PrintStack;
|
||||||
|
Keywords["expr"] = PrintExpr;
|
||||||
Keywords["import"] = Import;
|
Keywords["import"] = Import;
|
||||||
Keywords["export"] = Export;
|
Keywords["export"] = Export;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -87,10 +87,13 @@ public enum TokenType
|
||||||
Ref,
|
Ref,
|
||||||
Function,
|
Function,
|
||||||
Class,
|
Class,
|
||||||
Super,
|
Base,
|
||||||
|
|
||||||
TypeOf,
|
|
||||||
Print,
|
Print,
|
||||||
|
PrintStack,
|
||||||
|
PrintGlobals,
|
||||||
|
PrintExpr,
|
||||||
|
TypeOf,
|
||||||
|
|
||||||
Import,
|
Import,
|
||||||
Export
|
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.Collections;
|
||||||
using Qrakhen.Qamp.Core.Execution;
|
|
||||||
|
|
||||||
namespace Qrakhen.Qamp.Core.Values.Objects;
|
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 Function Function = function;
|
||||||
public StackRegister<OuterWrapper> PreValues = new();
|
public PushStack<Outer> Outers = new();
|
||||||
|
|
||||||
public override string ToString() => "{}";
|
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 OuterCount = outerCount;
|
||||||
public readonly int ArgumentCount = argumentCount;
|
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 Qrakhen.Qamp.Core.Values.Objects;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using Qrakhen.Qamp.Core.Collections;
|
using Qrakhen.Qamp.Core.Collections;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
|
||||||
namespace Qrakhen.Qamp.Core.Values;
|
namespace Qrakhen.Qamp.Core.Values;
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential, Size = sizeof(ulong))]
|
[StructLayout(LayoutKind.Sequential, Size = sizeof(ulong))]
|
||||||
public readonly struct Ptr
|
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;
|
[Serialized] public readonly Address Address;
|
||||||
|
|
||||||
|
|
@ -31,5 +32,13 @@ public readonly struct Ptr
|
||||||
Address = _register.Add(obj);
|
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}";
|
public override string ToString() => $"0x{Value}";
|
||||||
}
|
}
|
||||||
|
|
@ -15,6 +15,8 @@ public interface IValue
|
||||||
public readonly struct Value : IValue, IDebug<string>
|
public readonly struct Value : IValue, IDebug<string>
|
||||||
{
|
{
|
||||||
public static readonly Value Void = new Value();
|
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 ulong Unsigned;
|
||||||
[FieldOffset(0x00)] [Serialized] public readonly long Signed;
|
[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
|
[FieldOffset(0x0c)] [Serialized] public readonly uint Meta; // like lengths of arrays or lists
|
||||||
|
|
||||||
public T ValueType => Type;
|
|
||||||
|
|
||||||
private Value(T type) => Type = type;
|
private Value(T type) => Type = type;
|
||||||
|
|
||||||
public Value() : this(T.Void) { }
|
public Value() : this(T.Void) { }
|
||||||
|
|
@ -45,6 +45,19 @@ public readonly struct Value : IValue, IDebug<string>
|
||||||
|
|
||||||
public dynamic? Dynamic => AsDynamic();
|
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)
|
public bool Is(T type, bool exact = true)
|
||||||
{
|
{
|
||||||
return exact ? (Type & type) == type : (Type & type) > 0;
|
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)
|
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 Ptr.Value;
|
||||||
|
|
||||||
return Type switch {
|
return Type switch {
|
||||||
|
|
@ -88,13 +104,18 @@ public readonly struct Value : IValue, IDebug<string>
|
||||||
|
|
||||||
public override int GetHashCode() => Unsigned.GetHashCode();
|
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();
|
string type = Type.ToString();
|
||||||
if (Type.HasFlag(T.Pointer))
|
if (Type.HasFlag(T.Pointer))
|
||||||
type = Ptr.Value?.Type.ToString() ?? "NullPtr";
|
type = Ptr.Value?.Type.ToString() ?? "NullPtr";
|
||||||
|
|
||||||
return $"[{type}, {AsDynamic(false) ?? "null"}]";
|
return $"<{type}, {AsDynamic(false) ?? "null"}>";
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Debug(DebugLevel level = DebugLevel.None)
|
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;
|
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]
|
[Flags]
|
||||||
public enum ValueType
|
public enum ValueType
|
||||||
{
|
{
|
||||||
|
|
@ -11,6 +15,7 @@ public enum ValueType
|
||||||
Decimal = 0x0008,
|
Decimal = 0x0008,
|
||||||
Bool = 0x0010,
|
Bool = 0x0010,
|
||||||
|
|
||||||
|
Number = Unsigned | Signed | Decimal,
|
||||||
Integer = Unsigned | Signed | Char,
|
Integer = Unsigned | Signed | Char,
|
||||||
Primitive = Integer | Decimal | Bool,
|
Primitive = Integer | Decimal | Bool,
|
||||||
|
|
||||||
|
|
@ -29,7 +34,7 @@ public enum ValueType
|
||||||
Instance = Object | 0x0020,
|
Instance = Object | 0x0020,
|
||||||
Class = Object | 0x0100,
|
Class = Object | 0x0100,
|
||||||
Method = Class | Function,
|
Method = Class | Function,
|
||||||
PreValue = Object | 0x0200,
|
Outer = Object | 0x0200,
|
||||||
|
|
||||||
Module = 0xFFFF
|
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
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
# Visual Studio Version 18
|
# Visual Studio Version 18
|
||||||
VisualStudioVersion = 18.0.11123.170 d18.0
|
VisualStudioVersion = 18.0.11123.170
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
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}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Qrakhen.Qamp.Core", "Qrakhen.Qamp.Core\Qrakhen.Qamp.Core.csproj", "{CA6F4D4E-AF35-4CA6-BC53-C83277EEE46C}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Qrakhen.Qamp.CLI", "Qrakhen.Qamp.CLI\Qrakhen.Qamp.CLI.csproj", "{1D355F76-9D35-4CB2-BF8C-944B0842BC30}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Qrakhen.Qamp.CLI", "Qrakhen.Qamp.CLI\Qrakhen.Qamp.CLI.csproj", "{1D355F76-9D35-4CB2-BF8C-944B0842BC30}"
|
||||||
EndProject
|
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
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
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}.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.ActiveCfg = Release|Any CPU
|
||||||
{1D355F76-9D35-4CB2-BF8C-944B0842BC30}.Release|Any CPU.Build.0 = 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
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue