add incremental operators but fuck up the stack in the process

This commit is contained in:
Qrakhen 2025-12-04 13:49:56 +01:00
parent bef91ed928
commit 63cdc61c74
8 changed files with 231 additions and 47 deletions

21
LICENSE.txt Normal file
View File

@ -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.

View File

@ -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);

View File

@ -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);

View File

@ -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,

View File

@ -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;
} }

View File

@ -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>

151
Qrakhen.Qamp.Core/README.md Normal file
View File

@ -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.

View File

@ -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,