Merge branch 'dev' of git.sygade.eu:qrakhen/qamp into dev
This commit is contained in:
commit
9ec6f49450
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) [2025] [David Neumaier, Qrakhen]
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
@ -850,16 +850,16 @@ public class Digester : ISteppable<Token>
|
||||||
return Token.Error(errorMessage);
|
return Token.Error(errorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal bool Match(T type)
|
internal bool Match(T type, bool exact = true)
|
||||||
{
|
{
|
||||||
if (!Check(type))
|
if (!Check(type, exact))
|
||||||
return false;
|
return false;
|
||||||
Next();
|
Next();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal bool Check(T type)
|
internal bool Check(T type, bool exact = true)
|
||||||
=> Current.Type == type;
|
=> exact ? Current.Type == type : (Current.Type & type) > 0;
|
||||||
|
|
||||||
internal void ErrorAt(Token token, string errorMessage)
|
internal void ErrorAt(Token token, string errorMessage)
|
||||||
=> ReportError(token, errorMessage);
|
=> ReportError(token, errorMessage);
|
||||||
|
|
|
||||||
|
|
@ -76,15 +76,8 @@ public static class ExpressionParser
|
||||||
case TokenType.BitwiseRight:
|
case TokenType.BitwiseRight:
|
||||||
digester.Emit(OpCode.BitwiseRight);
|
digester.Emit(OpCode.BitwiseRight);
|
||||||
break;
|
break;
|
||||||
case TokenType.Increment:
|
|
||||||
digester.EmitConstant(new Value(1L));
|
|
||||||
digester.Emit(OpCode.Increment);
|
|
||||||
break;
|
|
||||||
case TokenType.Decrement:
|
|
||||||
digester.EmitConstant(new Value(1L));
|
|
||||||
digester.Emit(OpCode.Decrement);
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
|
throw new QampException($"Unexpected token in binary expression: {operatorType}");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -150,18 +143,6 @@ public static class ExpressionParser
|
||||||
digester.EmitDynamic(OpCode.Array, length); // digester.MakeConstant(new Value((long)length)));
|
digester.EmitDynamic(OpCode.Array, length); // digester.MakeConstant(new Value((long)length)));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void Increment(Digester digester, bool canAssign)
|
|
||||||
{
|
|
||||||
digester.ParseExpression();
|
|
||||||
digester.Emit(OpCode.Increment);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void Decrement(Digester digester, bool canAssign)
|
|
||||||
{
|
|
||||||
digester.ParseExpression();
|
|
||||||
digester.Emit(OpCode.Decrement);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void Accessor(Digester digester, bool canAssign)
|
static void Accessor(Digester digester, bool canAssign)
|
||||||
{
|
{
|
||||||
digester.ParseExpression();
|
digester.ParseExpression();
|
||||||
|
|
@ -227,8 +208,37 @@ public static class ExpressionParser
|
||||||
Set = OpCode.SetGlobal;
|
Set = OpCode.SetGlobal;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (canAssign && digester.Match(TokenType.Equal)) {
|
if (canAssign && digester.Match(TokenType.Assignment, false)) {
|
||||||
|
var token = digester.Previous.Type;
|
||||||
|
if (token == TokenType.Increment || token == TokenType.Decrement) {
|
||||||
|
// In the case of atomic change, we just forge the value ourselves.
|
||||||
|
digester.EmitConstant(new Value(1L));
|
||||||
|
} else {
|
||||||
|
// ... otherwise, we expect an expression on the right-hand side - parse that.
|
||||||
digester.ParseExpression();
|
digester.ParseExpression();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check whether this is just an assignment.
|
||||||
|
if (token != TokenType.Equal)
|
||||||
|
{
|
||||||
|
// Get the variable's value for the operation
|
||||||
|
digester.Emit(Get);
|
||||||
|
digester.EmitDynamic(variable);
|
||||||
|
|
||||||
|
// Append the operator
|
||||||
|
OpCode op = token switch
|
||||||
|
{
|
||||||
|
TokenType.PlusEqual or TokenType.Increment => OpCode.Add,
|
||||||
|
TokenType.MinusEqual or TokenType.Decrement => OpCode.Subtract,
|
||||||
|
TokenType.SlashEqual => OpCode.Divide,
|
||||||
|
TokenType.StarEqual => OpCode.Multiply,
|
||||||
|
TokenType.ModuloEqual => OpCode.Modulo,
|
||||||
|
_ => throw new QampException($"Unexpected token at variable assignment: {token}")
|
||||||
|
};
|
||||||
|
digester.Emit(op);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ultimately set the new value with whatever's on stack now.
|
||||||
digester.Emit(Set);
|
digester.Emit(Set);
|
||||||
digester.EmitDynamic(variable);
|
digester.EmitDynamic(variable);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -303,15 +313,15 @@ public static class ExpressionParser
|
||||||
_rules[TokenType.ContextClose] = new Rule(null, null, Weight.None);
|
_rules[TokenType.ContextClose] = new Rule(null, null, Weight.None);
|
||||||
_rules[TokenType.ArrayOpen] = new Rule(Array, Accessor, Weight.Call);
|
_rules[TokenType.ArrayOpen] = new Rule(Array, Accessor, Weight.Call);
|
||||||
_rules[TokenType.ArrayClose] = new Rule(null, null, Weight.None);
|
_rules[TokenType.ArrayClose] = new Rule(null, null, Weight.None);
|
||||||
_rules[TokenType.PlusEqual] = new Rule(null, Increment, Weight.None); // this is its own compilation operation as x++ calls binary and makes up the atomic change (1) on the spot.
|
_rules[TokenType.PlusEqual] = new Rule(null, null, Weight.None);
|
||||||
_rules[TokenType.MinusEqual] = new Rule(null, Decrement, Weight.None); // same as above
|
_rules[TokenType.MinusEqual] = new Rule(null, null, Weight.None);
|
||||||
_rules[TokenType.Colon] = new Rule(null, Dot, Weight.Call);
|
_rules[TokenType.Colon] = new Rule(null, Dot, Weight.Call);
|
||||||
_rules[TokenType.Comma] = new Rule(null, null, Weight.None);
|
_rules[TokenType.Comma] = new Rule(null, null, Weight.None);
|
||||||
_rules[TokenType.Dot] = new Rule(null, Dot, Weight.Call);
|
_rules[TokenType.Dot] = new Rule(null, Dot, Weight.Call);
|
||||||
_rules[TokenType.Minus] = new Rule(Modifier, Binary, Weight.Term);
|
_rules[TokenType.Minus] = new Rule(Modifier, Binary, Weight.Term);
|
||||||
_rules[TokenType.Plus] = new Rule(null, Binary, Weight.Term);
|
_rules[TokenType.Plus] = new Rule(null, Binary, Weight.Term);
|
||||||
_rules[TokenType.Increment] = new Rule(Binary, Binary, Weight.Call);
|
_rules[TokenType.Increment] = new Rule(Modifier, Binary, Weight.Call);
|
||||||
_rules[TokenType.Decrement] = new Rule(Binary, Binary, Weight.Call);
|
_rules[TokenType.Decrement] = new Rule(Modifier, Binary, Weight.Call);
|
||||||
_rules[TokenType.BitwiseAnd] = new Rule(Modifier, Binary, Weight.Term);
|
_rules[TokenType.BitwiseAnd] = new Rule(Modifier, Binary, Weight.Term);
|
||||||
_rules[TokenType.BitwiseOr] = new Rule(null, Binary, Weight.Term);
|
_rules[TokenType.BitwiseOr] = new Rule(null, Binary, Weight.Term);
|
||||||
_rules[TokenType.BitwiseXor] = new Rule(null, Binary, Weight.Term);
|
_rules[TokenType.BitwiseXor] = new Rule(null, Binary, Weight.Term);
|
||||||
|
|
|
||||||
|
|
@ -52,8 +52,8 @@ public enum OpCode
|
||||||
Greater = 0x51,
|
Greater = 0x51,
|
||||||
Less = 0x52,
|
Less = 0x52,
|
||||||
|
|
||||||
Increment = 0x70, // basically increment by, ++ simply 'makes up' the 1.
|
//Increment = 0x70, // basically increment by, ++ simply 'makes up' the 1.
|
||||||
Decrement = 0x71, // same here broren min
|
// = 0x71, // same here broren min
|
||||||
|
|
||||||
Array = 0xc0,
|
Array = 0xc0,
|
||||||
List = 0xc1,
|
List = 0xc1,
|
||||||
|
|
|
||||||
|
|
@ -87,6 +87,8 @@ public class Runner : IDisposable
|
||||||
_logger.Verbose($"OpCode: {opCode}");
|
_logger.Verbose($"OpCode: {opCode}");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
IO.Console.Write($"{opCode}: \n - {string.Join("\n - ", Stack.ToArray().Subset(0, Stack.Count))}");
|
||||||
|
|
||||||
if (TestFlag(opCode, Op.F_Operation)) {
|
if (TestFlag(opCode, Op.F_Operation)) {
|
||||||
Value result = ValueOperation.Resolve(PopOperation(opCode));
|
Value result = ValueOperation.Resolve(PopOperation(opCode));
|
||||||
Push(result);
|
Push(result);
|
||||||
|
|
@ -260,9 +262,9 @@ public class Runner : IDisposable
|
||||||
if (items.Is(T.Array))
|
if (items.Is(T.Array))
|
||||||
items.Ptr.As<Values.Objects.Array>()!.Set(index, value);
|
items.Ptr.As<Values.Objects.Array>()!.Set(index, value);
|
||||||
else if (items.Is(T.List))
|
else if (items.Is(T.List))
|
||||||
items.Ptr.As<Values.Objects.List>()!.Set(index, value);
|
items.Ptr.As<List>()!.Set(index, value);
|
||||||
else if (items.Is(T.Structure))
|
else if (items.Is(T.Structure))
|
||||||
items.Ptr.As<Values.Objects.Structure>()!.Set(index, value);
|
items.Ptr.As<Structure>()!.Set(index, value);
|
||||||
else
|
else
|
||||||
return Error($"Unsupported native accessor for type {items.Type}!");
|
return Error($"Unsupported native accessor for type {items.Type}!");
|
||||||
break;
|
break;
|
||||||
|
|
@ -274,7 +276,7 @@ public class Runner : IDisposable
|
||||||
if (!items.Is(T.List))
|
if (!items.Is(T.List))
|
||||||
return Error($"Can not add {value} to {items} - only lists :[] support the native add <+ operator.");
|
return Error($"Can not add {value} to {items} - only lists :[] support the native add <+ operator.");
|
||||||
|
|
||||||
items.Ptr.As<Values.Objects.List>()!.Add(value);
|
items.Ptr.As<List>()!.Add(value);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,6 @@
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
<BaseOutputPath>..\Build\</BaseOutputPath>
|
<BaseOutputPath>..\Build\</BaseOutputPath>
|
||||||
<Platforms>AnyCPU;x64</Platforms>
|
<Platforms>AnyCPU;x64</Platforms>
|
||||||
<DefineConstants>DEV;XD</DefineConstants>
|
<DefineConstants>LOG;DEV;XD</DefineConstants>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,151 @@
|
||||||
|
# Q&
|
||||||
|
|
||||||
|
## About
|
||||||
|
|
||||||
|
A single-pass byte code interpreter with the ability to pre-compile executables
|
||||||
|
into a format that can be efficiently executed by the runtime.
|
||||||
|
It serves many features, which are listed a bit further down below.
|
||||||
|
|
||||||
|
This would have officially been the fourth iteration of my language (sqript4),
|
||||||
|
but I thought I'd stop with the numbers as people would ask what happened to the
|
||||||
|
previous three languages.
|
||||||
|
|
||||||
|
Q& (or Qamp for Q Ampersand) was originally made for my contributions to the
|
||||||
|
Advent Of Code 2025, but I thought it's a nice and stable enough concept to be
|
||||||
|
sharing it to the public anyway.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
To build **Q&**, you need nothing more than a C# compiler that supports .NET10.
|
||||||
|
There are .NET8 compatible versions available, but they are not recommended,
|
||||||
|
as **Q&** utilizes all of the performance optimizations .NET10 has to offer.
|
||||||
|
|
||||||
|
After cloning, a simple `dotnet build ./` in the root directory will suffice.
|
||||||
|
|
||||||
|
You can execute pre-compiled .sqi files using `qamp.runtime.exe <filepath> [parameters]`,
|
||||||
|
or the REPL/CLI using `qamp.cli.exe [parameters]`.
|
||||||
|
If you want to build your own source into pre-compiled instruction files,
|
||||||
|
use `qamp.digest.exe <dirpath>` - the digester will look for an `Init()` function
|
||||||
|
declared in any of the files, and build the instructions from there.
|
||||||
|
|
||||||
|
## Language & Syntax
|
||||||
|
|
||||||
|
You are free to choose between the classic sqript-style dialect `*~ q <~ 0xf;` or
|
||||||
|
the standard dialect, which is strongly inspired by the C# standard: `var q = 0xf;`.
|
||||||
|
|
||||||
|
All of the following examples are written in the classic dialect `--dialect=sqr` or `-d=q`.
|
||||||
|
|
||||||
|
### Syntax
|
||||||
|
|
||||||
|
Most basic Usage:
|
||||||
|
```cs
|
||||||
|
# this is a comment.
|
||||||
|
*~ q <~ 12; # q == 12
|
||||||
|
*~ f <~ (n) <: n < 2 ? n : f(n-1) + f(n-2); # fibonacci
|
||||||
|
f(12); # 233
|
||||||
|
*~ a <~ [1, 2, 3]; # a = [1, 2, 3]
|
||||||
|
*~ x <~ a:0 + a:2; # x == 4
|
||||||
|
```
|
||||||
|
|
||||||
|
### Assignments
|
||||||
|
|
||||||
|
**Q&** has two distinct ways to assign a value to a variable:
|
||||||
|
- By reference `<&`, or
|
||||||
|
- By value `<=`.
|
||||||
|
|
||||||
|
Using the auto-assignment operator `<~`, **Q&** will choose the correct assignment based on what
|
||||||
|
type the right-hand expression consists of.
|
||||||
|
Primitive types will be assigned by value, any object values will be assigned by reference.
|
||||||
|
This is strongly inspired by the behaviour that most interpeters implement.
|
||||||
|
|
||||||
|
#### What happens if...
|
||||||
|
##### I assign an object by value?
|
||||||
|
The entire object will be deep-copied and transfered to the new location.
|
||||||
|
See it as a free clone method on basically anything that is considered an object.
|
||||||
|
##### I assign a primitive by reference?
|
||||||
|
So, assuming that `x` is a primitive (value type), given the instruction `*~ y <& x;`,
|
||||||
|
`y` will be assigned as a reference value _pointing_ to the location of `x` - somewhat like a
|
||||||
|
pointer in C, but a little smarter.
|
||||||
|
##### I do not praise the god emperor of mankind?
|
||||||
|
You will be duly punished.
|
||||||
|
|
||||||
|
#### Variable Declaration
|
||||||
|
|
||||||
|
```
|
||||||
|
*~ q <~ 0xf;
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Variable Declaration
|
||||||
|
|
||||||
|
#### Types
|
||||||
|
```
|
||||||
|
sig a; signed
|
||||||
|
uns b; unsigned
|
||||||
|
dec c; decimal
|
||||||
|
str d; string
|
||||||
|
chr e; character
|
||||||
|
bin f; boolean or binary, you can declare bitfields with:
|
||||||
|
bin:16 bits; custom bitfield type with 16 bits.
|
||||||
|
ref g; reference type, can be explicitely declared:
|
||||||
|
ref:sig aRef <& a;
|
||||||
|
ptr p; pointer, essentially the same as a ref, but in Q& ptr is the base type of all objects.
|
||||||
|
CustomType yourObject; self-explanatory
|
||||||
|
any type may be declared as [] array,
|
||||||
|
{} dictionary, or [:] list. (non-fixed collection)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Operators, Keywords & their aliases
|
||||||
|
```
|
||||||
|
*~ var
|
||||||
|
:~ const
|
||||||
|
<: return
|
||||||
|
:> continue
|
||||||
|
<+ increment / add
|
||||||
|
<- subtract / remove
|
||||||
|
:: print
|
||||||
|
?: typeof
|
||||||
|
<& assign reference
|
||||||
|
^~ base
|
||||||
|
.~ this
|
||||||
|
~( function
|
||||||
|
```
|
||||||
|
|
||||||
|
## Customization & Extras
|
||||||
|
|
||||||
|
Some more information about extending and customizing Q& to your needs.
|
||||||
|
|
||||||
|
### Writing Extension Methods
|
||||||
|
asdasd
|
||||||
|
|
||||||
|
### Implementing external Libraries
|
||||||
|
|
||||||
|
Implementation is easy.
|
||||||
|
Q& is written in C#, and supports basically anything you can wrap in C#.
|
||||||
|
All you have to do in order to implement a library is referencing the SDK dll
|
||||||
|
which should be located within your `./Build/` folder after building as described above.
|
||||||
|
|
||||||
|
Here's a quick example of how you would implement virtually any library:
|
||||||
|
```cs
|
||||||
|
using Qrakhen.Qamp.Core;
|
||||||
|
using System.Math;
|
||||||
|
|
||||||
|
Value Sqrt(Value number)
|
||||||
|
{
|
||||||
|
Assert.IsNumber(number);
|
||||||
|
|
||||||
|
if (number.IsDecimal)
|
||||||
|
return Math.Sqrt(number.AsDecimal);
|
||||||
|
if (number.IsSigned)
|
||||||
|
return Math.Sqrt(number.AsSigned);
|
||||||
|
if (number.IsUnsigned)
|
||||||
|
return Math.Sqrt(number.Unsigned);
|
||||||
|
throw new Qrakhen.Qamp.Core.QampException($"Unsupported value type {value}");
|
||||||
|
}
|
||||||
|
|
||||||
|
Injector injector = new();
|
||||||
|
injector.Register(scope: Scope.Global, name: "sqrt", params: [ ("number", ValueType.Number) ], returns: ValueType.Number);
|
||||||
|
injector.Export("desiredPath.sqi");
|
||||||
|
```
|
||||||
|
That's it - although this being a very simple example, more is possible.
|
||||||
|
I will add a few basic implementations of very common libraries in a separate repository soon.
|
||||||
|
|
||||||
|
|
@ -38,17 +38,15 @@ public enum TokenType
|
||||||
Operator = 1 << 8,
|
Operator = 1 << 8,
|
||||||
Minus = Operator | 1,
|
Minus = Operator | 1,
|
||||||
Plus = Operator | 2,
|
Plus = Operator | 2,
|
||||||
Decrement = Operator | 3,
|
Slash = Operator | 3,
|
||||||
Increment = Operator | 4,
|
Star = Operator | 4,
|
||||||
Slash = Operator | 5,
|
Modulo = Operator | 5,
|
||||||
Star = Operator | 6,
|
BitwiseAnd = Operator | 6,
|
||||||
Modulo = Operator | 7,
|
BitwiseOr = Operator | 7,
|
||||||
BitwiseAnd = Operator | 8,
|
BitwiseXor = Operator | 8,
|
||||||
BitwiseOr = Operator | 9,
|
BitwiseNot = Operator | 9,
|
||||||
BitwiseXor = Operator | 10,
|
BitwiseLeft = Operator | 10,
|
||||||
BitwiseNot = Operator | 11,
|
BitwiseRight = Operator | 11,
|
||||||
BitwiseLeft = Operator | 12,
|
|
||||||
BitwiseRight = Operator | 13,
|
|
||||||
|
|
||||||
Assignment = 1 << 9,
|
Assignment = 1 << 9,
|
||||||
Equal = Assignment | 1,
|
Equal = Assignment | 1,
|
||||||
|
|
@ -57,6 +55,8 @@ public enum TokenType
|
||||||
SlashEqual = Assignment | 4,
|
SlashEqual = Assignment | 4,
|
||||||
StarEqual = Assignment | 5,
|
StarEqual = Assignment | 5,
|
||||||
ModuloEqual = Assignment | 6,
|
ModuloEqual = Assignment | 6,
|
||||||
|
Decrement = Assignment | 7,
|
||||||
|
Increment = Assignment | 8,
|
||||||
|
|
||||||
Condition = 1 << 10,
|
Condition = 1 << 10,
|
||||||
Bang = Condition | 1,
|
Bang = Condition | 1,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue