From 63cdc61c748c83a5f1ce2e4f87ac7fb1535e2bac Mon Sep 17 00:00:00 2001 From: Qrakhen Date: Thu, 4 Dec 2025 13:49:56 +0100 Subject: [PATCH] add incremental operators but fuck up the stack in the process --- LICENSE.txt | 21 +++ Qrakhen.Qamp.Core/Compilation/Digester.cs | 8 +- .../Compilation/ExpressionParser.cs | 62 ++++--- Qrakhen.Qamp.Core/Execution/OpCode.cs | 4 +- Qrakhen.Qamp.Core/Execution/Runner.cs | 8 +- Qrakhen.Qamp.Core/Qrakhen.Qamp.Core.csproj | 2 +- Qrakhen.Qamp.Core/README.md | 151 ++++++++++++++++++ Qrakhen.Qamp.Core/Tokenization/TokenType.cs | 22 +-- 8 files changed, 231 insertions(+), 47 deletions(-) create mode 100644 LICENSE.txt create mode 100644 Qrakhen.Qamp.Core/README.md diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..82116ad --- /dev/null +++ b/LICENSE.txt @@ -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. \ No newline at end of file diff --git a/Qrakhen.Qamp.Core/Compilation/Digester.cs b/Qrakhen.Qamp.Core/Compilation/Digester.cs index 36cad0e..fcaf9b5 100644 --- a/Qrakhen.Qamp.Core/Compilation/Digester.cs +++ b/Qrakhen.Qamp.Core/Compilation/Digester.cs @@ -850,16 +850,16 @@ public class Digester : ISteppable 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; Next(); return true; } - internal bool Check(T type) - => Current.Type == type; + internal bool Check(T type, bool exact = true) + => exact ? Current.Type == type : (Current.Type & type) > 0; internal void ErrorAt(Token token, string errorMessage) => ReportError(token, errorMessage); diff --git a/Qrakhen.Qamp.Core/Compilation/ExpressionParser.cs b/Qrakhen.Qamp.Core/Compilation/ExpressionParser.cs index d0dce00..d466950 100644 --- a/Qrakhen.Qamp.Core/Compilation/ExpressionParser.cs +++ b/Qrakhen.Qamp.Core/Compilation/ExpressionParser.cs @@ -76,15 +76,8 @@ public static class ExpressionParser case TokenType.BitwiseRight: digester.Emit(OpCode.BitwiseRight); 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: + throw new QampException($"Unexpected token in binary expression: {operatorType}"); return; } } @@ -150,18 +143,6 @@ public static class ExpressionParser 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) { digester.ParseExpression(); @@ -227,8 +208,37 @@ public static class ExpressionParser Set = OpCode.SetGlobal; } - if (canAssign && digester.Match(TokenType.Equal)) { - digester.ParseExpression(); + 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(); + } + + // 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.EmitDynamic(variable); } else { @@ -303,15 +313,15 @@ public static class ExpressionParser _rules[TokenType.ContextClose] = new Rule(null, null, Weight.None); _rules[TokenType.ArrayOpen] = new Rule(Array, Accessor, Weight.Call); _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.MinusEqual] = new Rule(null, Decrement, Weight.None); // same as above + _rules[TokenType.PlusEqual] = new Rule(null, null, Weight.None); + _rules[TokenType.MinusEqual] = new Rule(null, null, Weight.None); _rules[TokenType.Colon] = new Rule(null, Dot, Weight.Call); _rules[TokenType.Comma] = new Rule(null, null, Weight.None); _rules[TokenType.Dot] = new Rule(null, Dot, Weight.Call); _rules[TokenType.Minus] = new Rule(Modifier, Binary, Weight.Term); _rules[TokenType.Plus] = new Rule(null, Binary, Weight.Term); - _rules[TokenType.Increment] = new Rule(Binary, Binary, Weight.Call); - _rules[TokenType.Decrement] = new Rule(Binary, Binary, Weight.Call); + _rules[TokenType.Increment] = new Rule(Modifier, Binary, Weight.Call); + _rules[TokenType.Decrement] = new Rule(Modifier, Binary, Weight.Call); _rules[TokenType.BitwiseAnd] = new Rule(Modifier, Binary, Weight.Term); _rules[TokenType.BitwiseOr] = new Rule(null, Binary, Weight.Term); _rules[TokenType.BitwiseXor] = new Rule(null, Binary, Weight.Term); diff --git a/Qrakhen.Qamp.Core/Execution/OpCode.cs b/Qrakhen.Qamp.Core/Execution/OpCode.cs index 142ffb6..6412fb6 100644 --- a/Qrakhen.Qamp.Core/Execution/OpCode.cs +++ b/Qrakhen.Qamp.Core/Execution/OpCode.cs @@ -52,8 +52,8 @@ public enum OpCode Greater = 0x51, Less = 0x52, - Increment = 0x70, // basically increment by, ++ simply 'makes up' the 1. - Decrement = 0x71, // same here broren min + //Increment = 0x70, // basically increment by, ++ simply 'makes up' the 1. + // = 0x71, // same here broren min Array = 0xc0, List = 0xc1, diff --git a/Qrakhen.Qamp.Core/Execution/Runner.cs b/Qrakhen.Qamp.Core/Execution/Runner.cs index 488eb4a..b96f06c 100644 --- a/Qrakhen.Qamp.Core/Execution/Runner.cs +++ b/Qrakhen.Qamp.Core/Execution/Runner.cs @@ -87,6 +87,8 @@ public class Runner : IDisposable _logger.Verbose($"OpCode: {opCode}"); #endif + IO.Console.Write($"{opCode}: \n - {string.Join("\n - ", Stack.ToArray().Subset(0, Stack.Count))}"); + if (TestFlag(opCode, Op.F_Operation)) { Value result = ValueOperation.Resolve(PopOperation(opCode)); Push(result); @@ -260,9 +262,9 @@ public class Runner : IDisposable if (items.Is(T.Array)) items.Ptr.As()!.Set(index, value); else if (items.Is(T.List)) - items.Ptr.As()!.Set(index, value); + items.Ptr.As()!.Set(index, value); else if (items.Is(T.Structure)) - items.Ptr.As()!.Set(index, value); + items.Ptr.As()!.Set(index, value); else return Error($"Unsupported native accessor for type {items.Type}!"); break; @@ -274,7 +276,7 @@ public class Runner : IDisposable if (!items.Is(T.List)) return Error($"Can not add {value} to {items} - only lists :[] support the native add <+ operator."); - items.Ptr.As()!.Add(value); + items.Ptr.As()!.Add(value); break; } diff --git a/Qrakhen.Qamp.Core/Qrakhen.Qamp.Core.csproj b/Qrakhen.Qamp.Core/Qrakhen.Qamp.Core.csproj index d1daa9a..ec0b452 100644 --- a/Qrakhen.Qamp.Core/Qrakhen.Qamp.Core.csproj +++ b/Qrakhen.Qamp.Core/Qrakhen.Qamp.Core.csproj @@ -7,6 +7,6 @@ true ..\Build\ AnyCPU;x64 - DEV;XD + LOG;DEV;XD diff --git a/Qrakhen.Qamp.Core/README.md b/Qrakhen.Qamp.Core/README.md new file mode 100644 index 0000000..d4e012f --- /dev/null +++ b/Qrakhen.Qamp.Core/README.md @@ -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 [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 ` - 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. + diff --git a/Qrakhen.Qamp.Core/Tokenization/TokenType.cs b/Qrakhen.Qamp.Core/Tokenization/TokenType.cs index 576e591..738824c 100644 --- a/Qrakhen.Qamp.Core/Tokenization/TokenType.cs +++ b/Qrakhen.Qamp.Core/Tokenization/TokenType.cs @@ -38,17 +38,15 @@ public enum TokenType Operator = 1 << 8, Minus = Operator | 1, Plus = Operator | 2, - Decrement = Operator | 3, - Increment = Operator | 4, - Slash = Operator | 5, - Star = Operator | 6, - Modulo = Operator | 7, - BitwiseAnd = Operator | 8, - BitwiseOr = Operator | 9, - BitwiseXor = Operator | 10, - BitwiseNot = Operator | 11, - BitwiseLeft = Operator | 12, - BitwiseRight = Operator | 13, + Slash = Operator | 3, + Star = Operator | 4, + Modulo = Operator | 5, + BitwiseAnd = Operator | 6, + BitwiseOr = Operator | 7, + BitwiseXor = Operator | 8, + BitwiseNot = Operator | 9, + BitwiseLeft = Operator | 10, + BitwiseRight = Operator | 11, Assignment = 1 << 9, Equal = Assignment | 1, @@ -57,6 +55,8 @@ public enum TokenType SlashEqual = Assignment | 4, StarEqual = Assignment | 5, ModuloEqual = Assignment | 6, + Decrement = Assignment | 7, + Increment = Assignment | 8, Condition = 1 << 10, Bang = Condition | 1,