Harmonii

From Esolang
Jump to navigation Jump to search

Harmonii is an esolang by User:BoundedBeans inspired by the .NET runtime patching library known as Harmony.

Fundamental concepts

The only datatype in Harmonii is the fing (function string). It's a reference to a string of characters that can be both treated as text and as an executable function written as Harmonii code. The only restriction on what can be in the text of a fing (other than the obvious one of being made of text characters) is that : and ; are like brackets and must be matched.

Important detail here is that a fing is a reference. This means, for example, you can modify the fing stored in a character without reassigning to the character, and duplicating a fing on the stack and modifying it modifies both copies.

Any character other than :; may be mapped to a fing, where the fing is Harmonii code to execute when the character is encountered.

Harmonii possesses a stack of fings, a map of characters to fings, the currently executing fing, an instruction pointer in that fing, and a call stack of fings being executed and the instruction pointers for them.

When Harmonii executes a fing, it doesn't use the preexisting meanings of commands that have been set to a fing, instead putting the currently executing fing and instruction pointer on the stack, setting the instruction pointer to zero, setting the currently executing fing to the one assigned to the character, executing that fing, and popping an entry off the call stack and assigning the currently executing string and instruction pointer to it. This means you cannot "capture" the meaning of a fing in another fing. If you need to use a character already assigned a meaning, you can always use :(character);` to execute its original meaning, and assign that to the character to give the character its original meaning back, albeit very very slightly slower by an insignificant amount.

An "exceptional fing" is a fing treated as an exception, traversing the call stack backwards until it finds a handler. The only way to make a handler is to apply a finalizer to the fing that throws the exceptional fing.

A fing can also contain any number of other fings attached to it, in four separate lists, the prefixes, postfixes, transpilers, and finalizers. These are known as patches. A fing cannot have multiple copies of the same fing attached to it, and a fing cannot be attached to itself. Fings attached to other fings can themselves have nested attachments, to an infinite degree, forming a kind of tree structure, though there isn't a huge use case for having a tree with more than two levels (you may occasionally want to patch a patch to modify its behavior, but it would be extremely rare to patch a patch of a patch, which would be 3 levels).

Recursion is banned, a single fing may not be on the call stack multiple times. Two or more fings with the same text, regardless of attachments, also may not be on the call stack.

If a fing is patched while it is executing, it will not take effect until it executes again. However, if it is patched while it's on the call stack somewhere, it will be restarted in its new form (this is the only way to do looping.)

Fings can be marked by certain commands as not being executable, usually string manipulation commands. They usually throw an exceptional fing with the text C when executed. However, they can be executed as normal as the result of a transpiler. This mechanic is to ensure the only method of looping is patching (because concatenation with a no-op would allow recursion even under the same text and recursion call stack restrictions).

Retrieving a variable that is not assigned a fing returns the empty fing (:;), and also assigns that to the variable.

Patches

When a patch is executed, it effectively is on the call stack instead of the fing with that patch attached. Thus, implementations of Harmonii will have to have some way of retrieving the parent fing to switch between patches, other patches, and the parent fing.

Prefixes

Whenever a fing is executed, its prefixes run before it, but after transpilers, in the order they were added. A prefix can execute ! to return instantly and skip all remaining prefixes and postfixes, and the main fing, but not finalizers (since they might need to do cleanup) or transpilers (since they would have already run). They're useful for altering the parameters (popped) with stack manipulation.

Postfixes

Whenever a fing finishes executing, its postfixes run afterwards, in the order they were added. There's no special functionality here. They're useful for altering the returned (pushed) valued with stack manipulation.

Transpilers

Whenever a fing is executed, a shallow copy containing only its text is pushed onto the stack, then its transpilers might run, in the order they were added. The transpilers don't run every time the fing is executed; they will run whenever a fing is executed for the first time after a transpiler is added or removed (you can force the transpilers to run again by adding and immediately removing a dummy transpiler).

Finalizers

Whenever a fing finishes executing, exits from an exceptional fing being thrown, or one of its prefixes tells it to exit early, the finalizers are run. For every finalizer, if any exceptional fing is present from the parent fing or the previous finalizer, the exceptional fing, followed by a new fing with the text A is pushed, otherwise two new fings with the text B are pushed. Then, the same structure is popped to either suppress the exceptional fing, rethrow it with optional modification, or throw a new exceptional fing.

Patch execution flow

  1. The transpilers might run first, in the order they were added. If one throws an exceptional fing, it will skip to the finalizers.
  2. Then the prefixes run, in the order they were added. Any one of them may use the ! command to skip all the remaining prefixes, but the finalizers will still run. If one throws an exceptional fing, it will skip to the finalizers.
  3. Then the main fing runs. If it throws an exceptional fing, it will skip to the finalizers.
  4. Then the postfixes run, in the order they were added. If one throws an exceptional fing, it will skip to the finalizers.
  5. Then the finalizers run, in the order they were added, regardless of whether the main method, or any of its patches executed previously, throw an exceptional fing.

Commands

The program is a series of concatenated commands. The whole program is treated as one fing, meaning higher entries on the call stack can modify and restart it, and it can obtain itself and reflect on itself with _.

Command Semantics
:(contents); Push the contents as a new fing onto the stack.
` Pops a single character fing off the stack, executes its original meaning. If the fing is not exactly one character long, throw an exceptional fing with the text A.
? Pops three fings A, B, and C off the stack. If A and B are equal, execute the first character of C, otherwise the second character. After the fing is done executing, return back to the character after the ?. If C is not exactly two characters, throw an exceptional fing with the text D.
= Pop two fings A and B off the stack. A must be a fing with only one character, otherwise throw an exceptional fing with the text B. Assign B to the character in A.
+ Pop a fing A off the stack. A must be a single character fing, otherwise throw an exceptional fing with the text E. Push the fing assigned to the character in A.
< Pop two fings A and B off the stack. Adds A as a prefix of B.
> Pop two fings A and B off the stack. Adds A as a postfix of B.
* Pop two fings A and B off the stack. Adds A as a transpiler of B.
# Pop two fings A and B off the stack. Adds A as a finalizer of B.
- Pop two fings A and B off the stack. Removes A from B's patches. If A is not attached to B, throw an exceptional fing with the text G.
" Pop two fings A and B off the stack. The text of A should match the regex [<>*#]@*, where the first character should be a type of patch represented as the command used to attach it, and the number of @s should correspond to the index of the patch in the list (which is based on the order the patches are attached, increasing by one for each new patch. The index of any given patch does not change or decrease when a different patch is removed. When a patch is removed, it can no longer be obtained with its index, and its index will not be reused by new patches). Pushes that patch of B onto the stack. If the patch does not exist, throw an exceptional fing with the text F.
u Pops a fing A off the stack, removes all of its patches and also resets the index counter for the fing (used by ") back to zero.
& Takes two fings and concatenates them, pushing the result as a new non-executable fing.
/ Pops a fing off the stack, pushes a new non-executable fing with its first character as the text.
\ Pops a fing off the stack, pushes a new non-executable fing with everything except its first character as the text.
~ Pops a fing off the stack, throws it as an exceptional fing.
_ Pops a fing A off the stack. Uses the length of A in characters as a 0-based index from the top of the call stack, pushes the fing at that point in the call stack.
. Pops a fing A off the stack, outputs the length of A in characters as a byte.
, Takes one byte of input, pushes a non-executable fing containing that number of @s concatenated.
^ Pops a fing and discards it.
| Swaps the top two fings on the stack.
$ Duplicates the top fing on the stack. Both are the same fing, so modifying one will also modify the other.
', space, line feed, carriage return, vertical tab, horizontal tab, form feed No operation.
! When executed with a prefix on the call stack, prevents the rest of the fing from executing, except for finalizers. Also returns early from all fings on the call stack above and including the prefix. Also runs finalizers, if any.
Capital A-Z Extension commands.

Example programs

Hello world!

:@@@@;$$&&$$&&$&.
:@@@@@;$&$&$$$$&&&&$:e;=.
:e;+:@@@@@@@;&$:l;=.
:l;+.
:l;+:@@@;&$:o;=.
:@@@@;$&$&$&$:_;=.
:o;+:@@@@@@@@;&.
:o;+.
:o;+:@@@;&.
:l;+.
:e;+\.
:_;+:@;&.

Truth-machine

  :/* Sets up a commonly used function to concatenate a string
      to itself 48 times (the ascii code of 0) */;$
:$$$&&&$$$&&&$$&&;:a;=
  :/* Sets up a function to remove the first 48 characters of a string.
      This will make it shorter to test for 0 or 1, but introduces the same
      amount of length in backslashes.

      So, we abuse the transpiler system to execute a dynamic string,
      and use the previous function to turn just one backslash into 48. */;$
:';$:b;=:^:\;a;*
  :/* The function for printing 0. */;$
::@;a.;:0;=
  :/* Empty function, as a consistent handle so we can remove it
      from the 1 function easily. */;$
:;:d;=
  :/* The function for printing 1.

      We use the mechanic that adding patches to a function lower down
      in the call stack restarts it to loop infinitely. */;$
::@;$a&.::1;+$:d;+-:d;+<;:c;=c;=
  :/* Add the dummy prefix function to 1 to avoid error :G;. */;$
:1;+:;$:d;=<
  :/* Simple conditional using our "remove first 48 characters" function
      to shorten the code. */;$
:01;:;,b?

Uncommented:

:$$$&&&$$$&&&$$&&;:a;=
:';$:b;=:^:\;a;*
::@;a.;:0;=
:;:d;=
::@;$a&.::1;+$:d;+-:d;+<;:c;=c;=
:1;+:;$:d;=<
:01;:;,b?