Source

This is just fucking twisted.

README contents:

Hexagony is (to the best of the author's knowledge) the first two-dimensional esoteric programming language on a hexagonal grid. Furthermore, the memory layout also resembles a (separate) hexagonal grid. The name is a portmanteau of "hexagon" and "agony", becauseā€¦ well, give programming in it a go.

Hexagony is Turing-complete.

Using Hexagony

There are a number of Hexagony implementations and a few ways to run them:

An honourable mention goes to Timwi's HexagonyColorer. While this doesn't let you run Hexagony code, it's a nifty tool for annotating code paths in Hexagony programs:

Hexagony program with colour-coded code paths

Overview

Hexagony has a number of important (and partially unique) concepts which need introduction.

Source code

The source code consists of printable ASCII characters and line feeds and is interpreted as a pointy-topped hexagonal grid, where each cell holds a single-character command (similar to how "normal" 2D languages like Befunge or ><> interpret their source as a rectangular grid). The source code must always be a regular hexagon. A convenient way to represent hexagonal layouts in ASCII is to insert a space after each cell and offset every other row. A hexagon of side-length 3 could be represented as

  . . .
 . . . .
. . . . .
 . . . .
  . . .

where each . could be a command (incidentally, . is a no-op in Hexagony). The next larger possible source code would be

   . . . .
  . . . . .
 . . . . . .
. . . . . . .
 . . . . . .
  . . . . .
   . . . .

Because of this restriction, the number of commands in the source code will always be a centred hexagonal number. For reference, the first 10 centred hexagonal numbers are:

1, 7, 19, 37, 61, 91, 127, 169, 217, 271

When reading a source file, Hexagony first strips all whitespace characters. Then each ` (backtick) are removed as well, but the characters after those backticks are marked with a "debug flag". Then the remaining source code is padded to the next centred hexagonal number with no-ops and rearranged it into a regular hexagon. This means that the spaces in the examples above were only inserted for cosmetic reasons but don't have to be included in the source code. The following three programs are identical:

  a b c
 d e f g
h . . . .
 . . . .
  . . .
abcdefgh...........
abcdefgh

But note that

abcdefg

would instead be the short form of

 a b
c d e
 f g

As an example for the debug flag, in the following code, the interpreter would print detailed debug information whenever the ? is executed:

  . . .
 . .`? .
. . . . . 
 . . . .
  . . .

The exact presentation of the debug information is up to the interpreter, but it should be possible to read off the following information:

These concepts are explained below.

Interpreters are allowed to omit this feature (and just strip backticks along with whitespace) provided they allow step-by-step debugging with access to the above information.

Control flow

Hexagony has 6 instruction pointers (IPs). They start out in the corners of the source code, pointing along the edge in the clockwise direction. Only one IP is active at any given time, initially the one in the top left corner (moving to the right). There are commands which let you switch to another IP, in which case the current IP will make another move (but not execute the next command), and then the new IP will start by executing its current command before making its first move. Each IP has an index from 0 to 5:

    0 . 1
   . . . .
  5 . . . 2
   . . . .
    4 . 3

The direction of an IP can be changed via several commands which resemble mirrors and branches.

The edges of the hexagon wrap around to the opposite edge. In all of the following grids, if an IP starts out on the a moving towards the b, the letters will be executed in alphabetical order before returning to a:

   . . . .          . a . .          . . k .          . g . .   
  a b c d e        . . b . .        . . j . .        . h . . a  
 . . . . . .      g . . c . .      . . i . . e      . i . . b . 
. . . . . . .    . h . . d . .    . . h . . d .    . j . . c . .
 f g h i j k      . i . . e .      . g . . c .      k . . d . . 
  . . . . .        . j . . f        f . . b .        . . e . .  
   . . . .          . k . .          . . a .          . f . .   

If the IP leaves the grid through a corner in the direction of the corner there are two possibilities:

-> . . . .   
  . . . . .  
 . . . . . . 
. . . . . . . ->
 . . . . . . 
  . . . . .  
-> . . . .   

If the current memory edge (see below) is positive, the IP will continue on the bottom row. If it's zero or negative, the IP will continue on the top row. For the other 5 corners, just rotate the picture. Note that if the IP leaves the grid in a corner but doesn't point at a corner, the wrapping happens normally. This means that there are two paths that lead to each corner:

      . . . . ->   
     . . . . .  
    . . . . . . 
-> . . . . . . .
    . . . . . . 
     . . . . .  
      . . . . ->

Memory model

Picture an infinite hexagonal grid (which is separate from the source code). Each edge of the grid has a value (a signed arbitrary-precision integer), which is initially zero. That is, the memory layout is essentially a line graph of a hexagonal grid.

The memory pointer (MP) points at one of the edges and has an orientation along that edge. At any time, there are three relevant edges: the one pointed at (the current memory edge), and its left and right neighbours (i.e. the edges connected to the vertex the MP's orientation points to).

It is possible to manipulate the current edge in several ways. The unary operators operate on the current edge only. The binary operators take the left and right neighbours as operands and store their result in the current edge. It is also possible to copy either the left or the right neighbour depending on the value of the current edge (essentially a ternary operator). The MP can reverse its direction or move to the left or right neighbour (without reversing its direction). There is also a conditional move, which chooses the neighbour to move to based on the value of the current edge.

Command list

The following is a complete reference of all commands available in Hexagony.

Special characters

Arithmetic

I/O

Control flow

The cells indicated as ?? are where they act as branches. In these cases, if the current memory edge is positive, the IP takes a 60 degree right turn (e.g. < turns E into SE). If the current memory edge is zero or negative, the IP takes a 60 degree left turn (e.g. < turns E into NE).
- [ switches to the previous IP (wrapping around from 0 to 5).
- ] switches to the next IP (wrapping around from 5 to 0).
- # takes the current memory edge modulo 6 and switches to the IP with that index.

Memory manipulation

Interpreter features

To run a program, invoke the interpreter with the source code's file name as a command-line argument, e.g.

$ ruby ./interpreter.rb ./examples/hw.hxg

The -d flag to activate ` annotations can be added in front of the source code, e.g.

$ ruby ./interpreter.rb -d ./examples/hw.hxg

The interpreter also has a verbose debug mode (like an additional debug level beyond activating ` annotations) which can be switched on with the command-line flag -D. If this flag is set, the interpreter will print detailed diagnostic information after every tick of the program.

It can also be invoked with -g N where N is a positive integer, in which case it will not run any code but instead print an "empty" source file (i.e. filled with .) of side-length N, e.g.

$ ruby ./interpreter.rb -g 5
     . . . . .
    . . . . . .
   . . . . . . .
  . . . . . . . .
 . . . . . . . . .
  . . . . . . . .
   . . . . . . .
    . . . . . .
     . . . . .

This is quite convenient for getting started when writing a larger program.


Tags: language   esoteric  

Last modified 12 July 2023