Regembly

MrJaydanOz

Everything is turing-complete if you just believe

...
Preview regex in Regex101

(For it to work you have set the type to ".NET 7.0", disable the "gm" flags and copy the regex into the text area)

Advanced Options
GitHub Repo
How the language works

Regembly is a programming language powered only by .NET's implementation of regex (regular expressions). You might ask "Is this turing-complete? Regex is not turing-complete." and it's... not, but turing complete asks that in theory you could run infinitly long programs for infinte time, Regembly can't doesn't do infinte loops. In practice, this language can do a lot that normal programing languages can do.

Fundamentals

To start your journey, we will first look at the end. The output of Regembly.

"match"
Console.Write("match");

In Regembly there is only one data type, which leaves the string syntax free to represent the output of the code. This is called a 'match'. Each match is retuned as a capture in a group which can be read from with the below C# code:

using System.Text.RegularExpressions;

// ...

Regex regex = new Regex("<REGEX GOES HERE>");

Match result = regex.Match("<CODE GOES HERE>");

if (result.Groups["error"].Length > 0)
    // Code had and error ...
else if (result.Groups["halt"].Length <= 0)
    // Code did not finish ...
else
{
    foreach (var match in result.Groups["match"].Captures)
        // ...
}

As mentioned before, Regembly only has one type and that one type is an unsigned integer... kind of. Internally, the value is the size of a stack that is pushed and popped from, which gives the only two math operations: 'increment' and 'decrement'.

a++
uint a = 2;
uint a = 0;

// ...

a += 2;

Can you guess what the above code does?

'a' is a variable that initialises with zero, it then has two added to it (not one). Variables can have any name that satisfies the pattern '\w+' (which means any letter, number or underscore combination). They are limited in the amount you can use for a given regex (A number input is placed above the generate button to change it). Their value can be incremented or decremented with the symbols '+' or '-' respectively. These symbols can be written with anyamount, the amount of these symbols correspond to the amount to change so: 'a+' adds one, 'a+++++' adds five and 'a---' subtracts three.

Since negative numbers are not supported, all variables are clamped to become zero or more. Because of this, decrementing a variable equal to '0' does nothing.

a---
uint a;

// ...

if (a <= 3)
    a = 0;
else
    a -= 3;
uint a;

// ...

for (int i = 0; i < 3 && a > 0; i++)
    a--;

Regembly supports 'goto' functionality for both forward and backwards jumping. Tags, like variables, can be named anything that matches '\w+'.

*Tag1

"ignore me"

Tag1:

Tag2:

"loop me"

*Tag2
goto Tag1;

Console.Write("ignore me");

Tag1:

Tag2:

Console.Write("loop me");

goto Tag2;

Jumping like this is what makes Regembly both so flexible and so limited in what it can compute. While jumping is unrestricted when it is available, it becomes unavailable when there are more jumps than there are characters in the code. When a jump is attempted but unavailable, the code will stop without the 'halt' group being matched, signifiying that the code has stopped short. This is the case in the example above.

When a 'goto' is searching for a the tag to jump to, it first searches for the closest with the same name before and then the closest after.

Tag: /* 3rd */
Tag: /* 2nd */
Tag: /* 1st */

*Tag

Tag: /* 4th */
Tag: /* 5th */
Tag: /* 6th */

Regembly has a different way of jumping that is not restriced in that way, it's called skipping and is represented with a greater than symbol '>'. Like with incrementing, skips can be used to any amount and still be a single command.

>>
"1" /* skipped */
"2" /* skipped */
"3" /* matched */
uint skip = 2;

if (skip > 0)
    skip--;
else
    Console.Write("1"); // skipped

if (skip > 0)
    skip--;
else
    Console.Write("2"); // skipped

if (skip > 0)
    skip--;
else
    Console.Write("3"); // matched

While skipping can be done theroetically infinte times, it is restriced in the sense that it can only jump forwards. Each single skip ('>') jumps over a single command (Not including itself). Commands include the previously mentioned matching ('"..."') and incrementing ('a+').

When using skips it's important to be aware of whitespace (any invisible character like the space bar or new lines). Whitespace will always separate commands. See the below example:

>>>>

vs

>> >>

The first case is one skip command that contains four individual skips and the second is two skip commands, each containing two individual skips. This is different because after the first case, four commands will be jumped over while after the second case, only one will be jumped over. This is because, the first skip command uses one of its containing skips to jump over the second, ignoring it.

Combinations

Continuing from the skip command, you wont have to count how many instructions to skip. Instead, you can use the scope block symbols '{' and '}'.

>{
    "1" /* skipped */
    "2" /* skipped */
}

{
    "3" /* matched */
    "4" /* matched */
}
uint skip = 1;

if (skip > 0)
    skip--;
else
{
    Console.Write("1"); // skipped
    Console.Write("2"); // skipped
}

if (skip > 0)
    skip--;
else
{
    Console.Write("3"); // matched
    Console.Write("4"); // matched
}

While by themselves they do nothing, if they are skipped then the whole contents of the block is skipped as if it were one command. (Recursive blocks are supported)

Regembly does also have a return stack, meaning functions are possible. Similar to the 'goto' symbol '*', using '&' and '$' will jump to and return from the tag respectively.

&DoAMatch

>{DoAMatch:
    "match"
$}

&DoAMatch
&DoAMatch

/* matches: "match", "match", "match" */
DoAMatch();

void DoAMatch();
{
    Console.Write("match");
    return;
}

DoAMatch();
DoAMatch();

// matches: "match", "match", "match"

To finish things off, Regembly has conditions and comparisons. These are related to the '?' symbol and are placed after a variable. If the condition is false, the command is the equivilent to a single skip.

a?"'a' is more than zero."
a!?"'a' is equal to zero."
if (a != 0)
    Console.Write("'a' is more than zero.");

if (a == 0)
    Console.Write("'a' is equal to zero.");
uint skip = 0;

if (a == 0)
    skip++;

if (skip > 0)
    skip--;
else
    Console.Write("'a' is more than zero.");

if (!(a == 0))
    skip++;

if (skip > 0)
    skip--;
else
    Console.Write("'a' is equal to zero.");

The condition by default only tests whether or not a variable is more than zero, but they can be combined with operations to add more functionality like shown below:

a?----"A"

Tests whether 'a' is larger than four.

a----?"B"

Subtracts four from 'a' and then tests whether its larger than zero.

a--?--"C"

Subtracts two from 'a' and then tests whether its larger than two.

if (a > 4)
    Console.Write("A");

Tests whether 'a' is larger than four.

a -= 4;
if (a > 0)
    Console.Write("B");

Subtracts four from 'a' and then tests whether its larger than zero.

a -= 2;
if (a > 2)
    Console.Write("C");

Subtracts two from 'a' and then tests whether its larger than two.

Operations can also read from other variables, three types are supported:

a=b++++

Adds the minimum of the value of 'b' and the amount of '+'s to 'a'.

a=b----

Subtracts the minimum of the value of 'b' and the amount of '-'s from 'a'.

a=b****

Adds the minimum of the value of 'b' and the amount of '*'s to 'a' and subtracts the same amount from 'b'. Effectively moving the value.

a += Math.Min(b, 4);

Adds the minimum of the value of 'b' and the amount of '+'s to 'a'.

a -= Math.Min(Math.Min(a, b), 4);

Subtracts the minimum of the value of 'b' and the amount of '-'s from 'a'.

uint transfer = Math.Min(b, 4);
a += transfer;
b -= transfer;

Adds the minimum of the value of 'b' and the amount of '*'s to 'a' and subtracts the same amount from 'b'. Effectively moving the value.

for (int i = Math.Min(b, 4); i >= 0; i--)
    a++;

Adds the minimum of the value of 'b' and the amount of '+'s to 'a'.

for (int i = Math.Min(b, 4); i >= 0 && a > 0; i--)
    a--;

Subtracts the minimum of the value of 'b' and the amount of '-'s from 'a'.

for (int i = Math.Min(b, 4); i >= 0; i--)
{
    a++;
    b--;
}

Adds the minimum of the value of 'b' and the amount of '*'s to 'a' and subtracts the same amount from 'b'. Effectively moving the value.

A little extra I added is to be able to index a match with a variable if you don't but a '?' between them.

a"0123456789"
/* Matches the value of 'a' if it's between 0 and 10. */

Finally, all of this last part can be combined together to create cursed garbage.

a+++=b**=c+++++"ABCDE"

I am MrJaydanOz, this is Regembly and regex is the compiler. Happy coding!

Preview regex in Regex101

(For it to work you have set the type to ".NET 7.0", disable the "gm" flags and copy the regex into the text area)