├── .gitignore ├── LICENSE ├── README.md ├── brute-forcer.rb ├── coords.rb ├── direction.rb ├── examples ├── fizzbuzz-compact.hxg ├── fizzbuzz-small.hxg ├── fizzbuzz.hxg ├── hw-compact.hxg ├── hw.hxg ├── hw2.hxg ├── prime-compact.hxg ├── prime-small.hxg └── prime.hxg ├── grid.rb ├── hexagony.rb ├── hexagony.sublime-project ├── interpreter.rb └── memory.rb /.gitignore: -------------------------------------------------------------------------------- 1 | mycode 2 | 3 | *.sublime-workspace -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Martin Büttner 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hexagony 2 | 3 | Hexagony is (to the best of the author's knowledge) the first two-dimensional [esoteric programming language](https://esolangs.org/wiki/Main_Page) 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. 4 | 5 | Hexagony is Turing-complete. 6 | 7 | ## Using Hexagony 8 | 9 | There are a number of Hexagony implementations and a few ways to run them: 10 | 11 | - The easiest way to your feet wet is SirBogman's fantastic online IDE [**hexagony.net**](https://hexagony.net/) (running its own JavaScript-based interpreter). 12 | - If you prefer an offline experience, Timwi's [**Esoteric IDE**](https://github.com/Timwi/EsotericIDE) also supports Hexagony (running its own C#-based interpreter). 13 | - If you're looking for a command-line interpreter, SirBogman has written a [**fast C#-based interpreter**](https://github.com/SirBogman/Hexagony). This interpreter also runs on [**code.golf**](https://code.golf/). 14 | - And finally, there's the original reference implementation in Ruby, which you can find in this very repo. Usage notes are at the bottom of this README. You can also use this interpreter online at [**Try it online!**](https://tio.run/##y0itSEzPz6v8/9/DOtU6wNDaQd863DrfWj/HGkgG6hdZ51inWBtZBxj8/w8A) 15 | 16 | An honourable mention goes to Timwi's [**HexagonyColorer**](https://github.com/Timwi/HexagonyColorer). While this doesn't let you *run* Hexagony code, it's a nifty tool for annotating code paths in Hexagony programs: 17 | 18 | ![Hexagony program with colour-coded code paths](https://camo.githubusercontent.com/57585cc1fdc2368caad878cbfa13862b3240a93f250831308a7dcc16f66e7b1e/687474703a2f2f692e737461636b2e696d6775722e636f6d2f65625931412e706e67) 19 | 20 | ## Overview 21 | 22 | Hexagony has a number of important (and partially unique) concepts which need introduction. 23 | 24 | ### Source code 25 | 26 | The source code consists of printable ASCII characters and line feeds and is interpreted as a [pointy-topped hexagonal grid](http://www.redblobgames.com/grids/hexagons/#basics), 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 27 | 28 | . . . 29 | . . . . 30 | . . . . . 31 | . . . . 32 | . . . 33 | 34 | where each `.` could be a command (incidentally, `.` is a no-op in Hexagony). The next larger possible source code would be 35 | 36 | . . . . 37 | . . . . . 38 | . . . . . . 39 | . . . . . . . 40 | . . . . . . 41 | . . . . . 42 | . . . . 43 | 44 | Because of this restriction, the number of commands in the source code will always be a [centred hexagonal number](https://oeis.org/A003215). For reference, the first 10 centred hexagonal numbers are: 45 | 46 | 1, 7, 19, 37, 61, 91, 127, 169, 217, 271 47 | 48 | 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: 49 | 50 | 51 | a b c 52 | d e f g 53 | h . . . . 54 | . . . . 55 | . . . 56 | 57 | 58 | 59 | abcdefgh........... 60 | 61 | 62 | 63 | abcdefgh 64 | 65 | But note that 66 | 67 | abcdefg 68 | 69 | would instead be the short form of 70 | 71 | a b 72 | c d e 73 | f g 74 | 75 | As an example for the debug flag, in the following code, the interpreter would print detailed debug information whenever the `?` is executed: 76 | 77 | . . . 78 | . .`? . 79 | . . . . . 80 | . . . . 81 | . . . 82 | 83 | The exact presentation of the debug information is up to the interpreter, but it should be possible to read off the following information: 84 | 85 | - The positions and directions of the instruction pointers. 86 | - Which instruction pointer is active. 87 | - The values and positions of all non-zero memory edges. 88 | - The position and orientation of the memory pointer. 89 | 90 | These concepts are explained below. 91 | 92 | 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. 93 | 94 | ### Control flow 95 | 96 | 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`: 97 | 98 | 0 . 1 99 | . . . . 100 | 5 . . . 2 101 | . . . . 102 | 4 . 3 103 | 104 | The direction of an IP can be changed via several commands which resemble mirrors and branches. 105 | 106 | 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`: 107 | 108 | . . . . . a . . . . k . . g . . 109 | a b c d e . . b . . . . j . . . h . . a 110 | . . . . . . g . . c . . . . i . . e . i . . b . 111 | . . . . . . . . h . . d . . . . h . . d . . j . . c . . 112 | f g h i j k . i . . e . . g . . c . k . . d . . 113 | . . . . . . j . . f f . . b . . . e . . 114 | . . . . . k . . . . a . . f . . 115 | 116 | If the IP leaves the grid through a corner *in the direction of the corner* there are two possibilities: 117 | 118 | -> . . . . 119 | . . . . . 120 | . . . . . . 121 | . . . . . . . -> 122 | . . . . . . 123 | . . . . . 124 | -> . . . . 125 | 126 | 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: 127 | 128 | . . . . -> 129 | . . . . . 130 | . . . . . . 131 | -> . . . . . . . 132 | . . . . . . 133 | . . . . . 134 | . . . . -> 135 | 136 | ### Memory model 137 | 138 | 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](https://en.wikipedia.org/wiki/Line_graph) of a hexagonal grid. 139 | 140 | 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). 141 | 142 | 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. 143 | 144 | ## Command list 145 | 146 | The following is a complete reference of all commands available in Hexagony. 147 | 148 | ### Special characters 149 | 150 | - **Letters:** All 52 letter characters are reserved and will set the current memory cell to their ASCII code. 151 | - `.` is a no-op: the IP will simply pass through. 152 | - `@` terminates the program. 153 | 154 | ### Arithmetic 155 | 156 | - `0-9` will multiply the current memory edge by 10 and add the corresponding digit. If the current edge has a negative value, the digit is subtracted instead of added. This allows you to write decimal numbers in the source code despite each digit being processed separately. 157 | - `)` increments the current memory edge. 158 | - `(` decrements the current memory edge. 159 | - `+` sets the current memory edge to the sum of the left and right neighbours. 160 | - `-` sets the current memory edge to the difference of the left and right neighbours (`left - right`). 161 | - `*` sets the current memory edge to the product of the left and right neighbours. 162 | - `:` sets the current memory edge to the quotient of the left and right neighbours (`left / right`, rounded towards negative infinity). 163 | - `%` sets the current memory edge to the modulo of the left and right neighbours (`left % right`, the sign of the result is the same as the sign of `right`). 164 | - `~` multiplies the current memory edge by `-1`. 165 | 166 | ### I/O 167 | 168 | - `,` reads a single byte from STDIN and sets the current memory edge to its value. Returns `-1` once EOF is reached. 169 | - `?` reads and discards from STDIN until a digit, a `-` or a `+` is found. Then reads as many bytes as possible to form a valid (signed) decimal integer and sets the current memory edge to its value. The next byte after the number is not consumed by this command and can be read with `,`. Returns `0` if EOF is reached without finding a valid number. 170 | - `;` writes the current memory edge's value (modulo 256) to STDOUT as a byte. 171 | - `!` writes the decimal representation of the current memory edge to STDOUT. 172 | 173 | ### Control flow 174 | 175 | - `$` is a jump. When executed, the IP completely ignores the next command in its current direction. This is like Befunge's `#`. 176 | - `_`, `|`, `/`, `\` are mirrors. They reflect the IP in the direction you'd expect. For completeness, the following table shows how they deflect an incoming IP. The top row corresponds to the current direction of the IP, the left column to the mirror, and the table cell shows the outgoing direction of the IP: 177 | 178 | cmd E SE SW W NW NE 179 | 180 | / NW W SW SE E NE 181 | \ SW SE E NE NW W 182 | _ E NE NW W SW SE 183 | | W SW SE E NE NW 184 | 185 | - `<` and `>` act as either mirrors or branches, depending on the incoming direction: 186 | 187 | cmd E SE SW W NW NE 188 | 189 | < ?? NW W E W SW 190 | > W E NE ?? SE E 191 | 192 | 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`). 193 | - `[` switches to the previous IP (wrapping around from `0` to `5`). 194 | - `]` switches to the next IP (wrapping around from `5` to `0`). 195 | - `#` takes the current memory edge modulo `6` and switches to the IP with that index. 196 | 197 | ### Memory manipulation 198 | 199 | - `{` moves the MP to the left neighbour. 200 | - `}` moves the MP to the right neighbour. 201 | - `"` moves the MP backwards and to the left. This is equivalent to `=}=`. 202 | - `'` moves the MP backwards and to the right. This is equivalent to `={=`. 203 | - `=` reverses the direction of the MP. (This doesn't affect the current memory edge, but changes which edges are considered the left and right neighbour.) 204 | - `^` moves the MP to the left neighbour if the current edge is zero or negative and to the right neighbour if it's positive. 205 | - `&` copies the value of left neighbour into the current edge if the current edge is zero or negative and the value of the right neighbour if it's positive. 206 | 207 | ## Interpreter features 208 | 209 | To run a program, invoke the interpreter with the source code's file name as a command-line argument, e.g. 210 | 211 | $ ruby ./interpreter.rb ./examples/hw.hxg 212 | 213 | The `-d` flag to activate `` ` `` annotations can be added in front of the source code, e.g. 214 | 215 | $ ruby ./interpreter.rb -d ./examples/hw.hxg 216 | 217 | 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. 218 | 219 | 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. 220 | 221 | $ ruby ./interpreter.rb -g 5 222 | . . . . . 223 | . . . . . . 224 | . . . . . . . 225 | . . . . . . . . 226 | . . . . . . . . . 227 | . . . . . . . . 228 | . . . . . . . 229 | . . . . . . 230 | . . . . . 231 | 232 | This is quite convenient for getting started when writing a larger program. 233 | -------------------------------------------------------------------------------- /brute-forcer.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | require_relative 'hexagony' 4 | require_relative 'grid' 5 | require 'stringio' 6 | 7 | [')', '('].each do |count_char| 8 | (0..5).each do |inc_pos| 9 | ([*1..5]-[inc_pos]).each do |out_pos| 10 | ([*1..5]-[inc_pos,out_pos]).each do |out2_pos| 11 | code = ' '*6 12 | code[inc_pos] = count_char 13 | code[out_pos] = '!' 14 | code[out2_pos] = ';' 15 | 16 | $stderr.puts code 17 | 18 | (('"'..'~').to_a-"`?,@".chars).repeated_permutation(3) do |pos| 19 | this_code = code.clone 20 | pos.each do |c| this_code.sub!(' ',c) end 21 | 22 | in_stream = StringIO.new('') 23 | out_stream = StringIO.new 24 | errored = false 25 | begin 26 | aborted = Hexagony.run(this_code, 0, in_stream, out_stream, 35) 27 | rescue 28 | next 29 | end 30 | 31 | if aborted && (out_stream.string =~ /^1([ \n\t,])2(\1)3/) 32 | puts this_code 33 | $stderr.puts this_code 34 | end 35 | end 36 | end 37 | end 38 | end 39 | end -------------------------------------------------------------------------------- /coords.rb: -------------------------------------------------------------------------------- 1 | class PointAxial 2 | attr_accessor :q, :r 3 | 4 | def initialize(q, r) 5 | @q = q 6 | @r = r 7 | end 8 | 9 | def self.from_string(string) 10 | coords = string.split.map(&:to_i) 11 | PointAxial.new(coords[0], coords[1]) 12 | end 13 | 14 | def to_cube 15 | PointCube.new(@q, -@q-@r, @r) 16 | end 17 | 18 | def +(other) 19 | if other.is_a?(PointAxial) 20 | return PointAxial.new(@q+other.q, @r+other.r) 21 | end 22 | end 23 | 24 | def -(other) 25 | if other.is_a?(PointAxial) 26 | return PointAxial.new(@q-other.q, @r-other.r) 27 | end 28 | end 29 | 30 | def coerce(other) 31 | return self, other 32 | end 33 | 34 | def to_s 35 | "(%d,%d)" % [@q, @r] 36 | end 37 | end 38 | 39 | class PointCube 40 | attr_accessor :x, :y, :z 41 | 42 | def initialize(x, y, z) 43 | @x = x 44 | @y = y 45 | @z = z 46 | end 47 | 48 | def self.from_string(string) 49 | coords = string.split.map(&:to_i) 50 | PointCube.new(coords[0], coords[1], coords[2]) 51 | end 52 | 53 | def to_axial 54 | PointAxial.new(@x, @z) 55 | end 56 | 57 | def +(other) 58 | if other.is_a?(PointCube) 59 | return PointCube.new(@x+other.x, @y+other.y, @z+other.z) 60 | end 61 | end 62 | 63 | def -(other) 64 | if other.is_a?(PointCube) 65 | return PointCube.new(@x-other.x, @y-other.y, @z-other.z) 66 | end 67 | end 68 | 69 | def coerce(other) 70 | return self, other 71 | end 72 | 73 | def to_s 74 | "(%d,%d,%d)" % [@x, @y, @z] 75 | end 76 | end -------------------------------------------------------------------------------- /direction.rb: -------------------------------------------------------------------------------- 1 | require_relative 'coords' 2 | 3 | class NorthEast 4 | def right() East.new end 5 | def left() NorthWest.new end 6 | 7 | def reflect_diag_up() NorthEast.new end 8 | def reflect_diag_down() West.new end 9 | def reflect_hori() SouthEast.new end 10 | def reflect_vert() NorthWest.new end 11 | def reflect_branch_left(right) SouthWest.new end 12 | def reflect_branch_right(right) East.new end 13 | 14 | def reverse() SouthWest.new end 15 | def vec() PointAxial.new(1,-1) end 16 | 17 | def ==(other) other.is_a?(NorthEast) end 18 | def coerce(other) return self, other end 19 | 20 | def to_s() "North East" end 21 | end 22 | 23 | class NorthWest 24 | def right() NorthEast.new end 25 | def left() West.new end 26 | 27 | def reflect_diag_up() East.new end 28 | def reflect_diag_down() NorthWest.new end 29 | def reflect_hori() SouthWest.new end 30 | def reflect_vert() NorthEast.new end 31 | def reflect_branch_left(right) West.new end 32 | def reflect_branch_right(right) SouthEast.new end 33 | 34 | def reverse() SouthEast.new end 35 | def vec() PointAxial.new(0,-1) end 36 | 37 | def ==(other) other.is_a?(NorthWest) end 38 | def coerce(other) return self, other end 39 | 40 | def to_s() "North West" end 41 | end 42 | 43 | class West 44 | def right() NorthWest.new end 45 | def left() SouthWest.new end 46 | 47 | def reflect_diag_up() SouthEast.new end 48 | def reflect_diag_down() NorthEast.new end 49 | def reflect_hori() West.new end 50 | def reflect_vert() East.new end 51 | def reflect_branch_left(right) East.new end 52 | def reflect_branch_right(right) right ? NorthWest.new : SouthWest.new end 53 | 54 | def reverse() East.new end 55 | def vec() PointAxial.new(-1,0) end 56 | 57 | def ==(other) other.is_a?(West) end 58 | def coerce(other) return self, other end 59 | 60 | def to_s() "West" end 61 | end 62 | 63 | class SouthWest 64 | def right() West.new end 65 | def left() SouthEast.new end 66 | 67 | def reflect_diag_up() SouthWest.new end 68 | def reflect_diag_down() East.new end 69 | def reflect_hori() NorthWest.new end 70 | def reflect_vert() SouthEast.new end 71 | def reflect_branch_left(right) West.new end 72 | def reflect_branch_right(right) NorthEast.new end 73 | 74 | def reverse() NorthEast.new end 75 | def vec() PointAxial.new(-1,1) end 76 | 77 | def ==(other) other.is_a?(SouthWest) end 78 | def coerce(other) return self, other end 79 | 80 | def to_s() "South West" end 81 | end 82 | 83 | class SouthEast 84 | def right() SouthWest.new end 85 | def left() East.new end 86 | 87 | def reflect_diag_up() West.new end 88 | def reflect_diag_down() SouthEast.new end 89 | def reflect_hori() NorthEast.new end 90 | def reflect_vert() SouthWest.new end 91 | def reflect_branch_left(right) NorthWest.new end 92 | def reflect_branch_right(right) East.new end 93 | 94 | def reverse() NorthWest.new end 95 | def vec() PointAxial.new(0,1) end 96 | 97 | def ==(other) other.is_a?(SouthEast) end 98 | def coerce(other) return self, other end 99 | 100 | def to_s() "South East" end 101 | end 102 | 103 | class East 104 | def right() SouthEast.new end 105 | def left() NorthEast.new end 106 | 107 | def reflect_diag_up() NorthWest.new end 108 | def reflect_diag_down() SouthWest.new end 109 | def reflect_hori() East.new end 110 | def reflect_vert() West.new end 111 | def reflect_branch_left(right) right ? SouthEast.new : NorthEast.new end 112 | def reflect_branch_right(right) West.new end 113 | 114 | def reverse() West.new end 115 | def vec() PointAxial.new(1,0) end 116 | 117 | def ==(other) other.is_a?(East) end 118 | def coerce(other) return self, other end 119 | 120 | def to_s() "East" end 121 | end -------------------------------------------------------------------------------- /examples/fizzbuzz-compact.hxg: -------------------------------------------------------------------------------- 1 | d{$>){*./;\.}<._.zi...><{}.;/;$@-/=.*F;>8M'<$<..'_}....>.3'%<}'>}))'%<..._>_.'<$.....};u..}....{B.;..;.!<'..>z;/ -------------------------------------------------------------------------------- /examples/fizzbuzz-small.hxg: -------------------------------------------------------------------------------- 1 | d { $ > ) { * 2 | . / ; \ . } < . 3 | _ . z i . . . > < 4 | { } . ; / ; $ @ - / 5 | = . * F ; > 8 M ' < $ 6 | < . . ' _ } . . . . > . 7 | 3 ' % < } ' > } ) ) ' % < 8 | . . . _ > _ . ' < $ . . 9 | . . . } ; u . . } . . 10 | . . { B . ; . . ; . 11 | ! < ' . . > z ; / 12 | . . . . . . . . 13 | . . . . . . . -------------------------------------------------------------------------------- /examples/fizzbuzz.hxg: -------------------------------------------------------------------------------- 1 | . . . . . . . . . . . . . . 2 | . . . . . . . . . . . . . . . 3 | . . . . . . . . . . . . . . . . 4 | . . . . . . . . . . . . . . . . . 5 | . . . . . . . . . . . . . . . . . . 6 | . . . . . . . . . . . . . . . . . . . 7 | . . . . . . . . . . . . . . . . . . . . 8 | . . . . . . . . . . > z \ . . . . . . . . 9 | . . . . . . . . . . ; . ; . . . . . . > ; \ 10 | . . . . . . . . . . i . ; . . . . . . u . z . 11 | . . . . . . . . . . ; . } . . . . . . ; . ; . . 12 | . . . . . . . . . . F . . . . . . . . B . ; . . . 13 | . . . . . . . . . . ' . | . . . . . . ' . } . . . . 14 | d { $ > ) { * 3 ' % < > $ > } ) ) ' % < . . . . . . . 15 | . . . . . . . . . . _ . . . . . . . . . $ . . . . . 16 | . | . . . . . . / . = { } * < . . . . | . . . . . 17 | . } . . . . . . | . . . . . > ' . . < . . . . . 18 | . > - ; 8 M ' < $ ! = { } < . . . . . . . . . 19 | @ . . . . . . . . . . . . . . . . . . . . . 20 | . . . . . . . . . . . . . . . . . . . . . 21 | . . . . . . . . . . . . . . . . . . . . 22 | . . . . . . . . . . . . . . . . . . . 23 | . . . . . . . . . . . . . . . . . . 24 | . . . . . . . . . . . . . . . . . 25 | . . . . . . . . . . . . . . . . 26 | . . . . . . . . . . . . . . . 27 | . . . . . . . . . . . . . . -------------------------------------------------------------------------------- /examples/hw-compact.hxg: -------------------------------------------------------------------------------- 1 | H;e;l;d;*;r;o;Wl;;o;*433;@.>;23<\4;*/ -------------------------------------------------------------------------------- /examples/hw.hxg: -------------------------------------------------------------------------------- 1 | H ; e ; 2 | l ; d ; * 3 | ; r ; o ; W 4 | l ; ; o ; * 4 5 | 3 3 ; @ . > 6 | ; 2 3 < \ 7 | 4 ; * / -------------------------------------------------------------------------------- /examples/hw2.hxg: -------------------------------------------------------------------------------- 1 | H ; e ; 2 | ; l ; d ; 3 | r ; o ; W } 4 | l ; ; o ; * 4 5 | = + ) ; @ > 6 | ; 2 3 < \ 7 | 4 ; * / -------------------------------------------------------------------------------- /examples/prime-compact.hxg: -------------------------------------------------------------------------------- 1 | )}?}.=(..]=}='.}.}~./%*..&.=&{.<......=|>(<..}!=...&@\[ -------------------------------------------------------------------------------- /examples/prime-small.hxg: -------------------------------------------------------------------------------- 1 | ) } ? } . 2 | = ( . . ] = 3 | } = ' . } . } 4 | ~ . / % * . . & 5 | . = & { . < . . . 6 | . . . = | > ( < 7 | . . } ! = . . 8 | . & @ \ [ . 9 | . . . . . -------------------------------------------------------------------------------- /examples/prime.hxg: -------------------------------------------------------------------------------- 1 | . . . . . . . . . . . . . 2 | . . . . . . . . . . . . . . 3 | . . . . . . . . . . . . . . . 4 | . . . . . . . . . . @ . . . . . 5 | . . . . . . . . . . ! . . . . . . 6 | . . . . . . . . . . % . . . . . . . 7 | . . . . . . . . . . ' . . . . . . . . 8 | . . . . . . . . . . & . . . . . . . . . 9 | . . . . . . . . . . { . . . . . . . . . . 10 | . . . . . . . . . . * . . . . . . . . . . . 11 | . . . . . . . . . . = . . . . . . . . . . . . 12 | . . . . . . . . . . } . . . . . . . . . . . . . 13 | 1 } ? } = & { < . . & . . . . . . . . . . . . . . 14 | . . . . . . . > ( < . . . . . . . . . . . . . . 15 | . . . . . . = . . } . . . . . . . . . . . . . 16 | . . . . . } . . . = . . . . . . . . . . . . 17 | . . . . | . . . . | . . . . . . . . . . . 18 | . . . . * . . . ) . . . . . . . . . . . 19 | . . . . = . . & . . . . . . . . . . . 20 | . . . . > } < . . . . . . . . . . . 21 | . . . . . . . . . . . . . . . . . 22 | . . . . . . . . . . . . . . . . 23 | . . . . . . . . . . . . . . . 24 | . . . . . . . . . . . . . . 25 | . . . . . . . . . . . . . -------------------------------------------------------------------------------- /grid.rb: -------------------------------------------------------------------------------- 1 | require_relative 'coords' 2 | 3 | class Grid 4 | attr_reader :size 5 | 6 | OPERATORS = { 7 | '!' => [:output_int], 8 | '"' => [:mp_rev_left], 9 | '#' => [:choose_ip], 10 | '$' => [:jump], 11 | '%' => [:mod], 12 | '&' => [:mem_cpy], 13 | '\'' => [:mp_rev_right], 14 | '(' => [:dec], 15 | ')' => [:inc], 16 | '*' => [:mul], 17 | '+' => [:add], 18 | ',' => [:input_char], 19 | '-' => [:sub], 20 | '.' => [:nop], 21 | '/' => [:mirror_diag_up], 22 | '0' => [:digit, 0], '1' => [:digit, 1], '2' => [:digit, 2], '3' => [:digit, 3], '4' => [:digit, 4], '5' => [:digit, 5], '6' => [:digit, 6], '7' => [:digit, 7], '8' => [:digit, 8], '9' => [:digit, 9], 23 | ':' => [:div], 24 | ';' => [:output_char], 25 | '<' => [:branch_left], 26 | '=' => [:mp_reverse], 27 | '>' => [:branch_right], 28 | '?' => [:input_int], 29 | '@' => [:terminate], 30 | #'A' , 31 | # ... These will not be assigned and will set their ASCII value to the current memory cell. 32 | #'Z' , 33 | '[' => [:prev_ip], 34 | '\\' => [:mirror_diag_down], 35 | ']' => [:next_ip], 36 | '^' => [:mp_branch], 37 | '_' => [:mirror_hori], 38 | #'`' , This will not be assigned as it's used for debug annotations. 39 | #'a' , 40 | # ... These will not be assigned and will set their ASCII value to the current memory cell. 41 | #'z' , 42 | '{' => [:mp_left], 43 | '|' => [:mirror_vert], 44 | '}' => [:mp_right], 45 | '~' => [:neg], 46 | } 47 | 48 | OPERATORS.default_proc = proc do |hash, key| 49 | [:mem_set, key.ord] 50 | end 51 | 52 | def initialize size 53 | @size = size 54 | @grid = Array.new(2*@size-1) {|j| 55 | [[[:nop]]]*(2*@size-1 - (@size-1 - j).abs) 56 | } 57 | end 58 | 59 | def self.from_string(string) 60 | src_dbg = string.gsub(/\s/,'') 61 | src = src_dbg.gsub(/`/,'') 62 | 63 | 64 | # Find size of the grid as the smallest regular hexagon which 65 | # is not smaller than the source code. 66 | size = 1 67 | size += 1 while 3*size*(size-1) + 1 < src.size 68 | 69 | src_dbg += '.'*(3*size*(size-1) + 1 - src.size) 70 | 71 | grid = Grid.new(size) 72 | 73 | debug = false 74 | ops = [] 75 | 76 | src_dbg.each_char {|c| 77 | if c == '`' 78 | debug = true 79 | else 80 | ops << [OPERATORS[c], debug] 81 | debug = false 82 | end 83 | } 84 | 85 | grid.fill! ops 86 | 87 | grid 88 | end 89 | 90 | def fill! data 91 | i = -1 92 | @grid.map! { |line| 93 | line.map! { 94 | data[i+=1] 95 | } 96 | } 97 | end 98 | 99 | def get coords 100 | i, j = axial_to_index coords 101 | 102 | if i && j 103 | @grid[i][j] 104 | else 105 | nil 106 | end 107 | end 108 | 109 | def set coords, value 110 | i, j = axial_to_index coords 111 | 112 | @grid[i][j] = value if i && j 113 | end 114 | 115 | def axial_to_index coords 116 | x = coords.q 117 | z = coords.r 118 | y = -x-z 119 | return nil if [x.abs, y.abs, z.abs].max >= @size 120 | 121 | i = z + @size-1 122 | j = x + [i, @size-1].min 123 | return [i, j] 124 | end 125 | 126 | def to_s 127 | @grid.map{|line| 128 | ' '*(2*@size-1 - line.size) + line.map{|c,d| (d ? '`' : ' ') + (OPERATORS.invert[c]||c[1].chr(Encoding::UTF_8))}*'' 129 | }*$/ 130 | end 131 | end -------------------------------------------------------------------------------- /hexagony.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | require_relative 'grid' 4 | require_relative 'memory' 5 | require_relative 'coords' 6 | require_relative 'direction' 7 | 8 | class Hexagony 9 | 10 | class ProgramError < Exception; end 11 | 12 | def self.run(src, debug_level=0, in_str=$stdin, out_str=$stdout, max_ticks=-1) 13 | new(src, debug_level, in_str, out_str, max_ticks).run 14 | end 15 | 16 | def initialize(src, debug_level=false, in_str=$stdin, out_str=$stdout, max_ticks=-1) 17 | @debug_level = debug_level 18 | @in_str = in_str 19 | @out_str = out_str 20 | @max_ticks = max_ticks 21 | @debug_tick = false 22 | @grid = Grid.from_string(src) 23 | size = @grid.size 24 | 25 | @memory = Memory.new 26 | 27 | @ips = [ 28 | [PointAxial.new(0,-size+1), East.new], 29 | [PointAxial.new(size-1,-size+1), SouthEast.new], 30 | [PointAxial.new(size-1,0), SouthWest.new], 31 | [PointAxial.new(0,size-1), West.new], 32 | [PointAxial.new(-size+1,size-1), NorthWest.new], 33 | [PointAxial.new(-size+1,0), NorthEast.new] 34 | ] 35 | @active_ip = @new_ip = 0 36 | 37 | @tick = 0 38 | end 39 | 40 | def dir 41 | @ips[@active_ip][1] 42 | end 43 | 44 | def coords 45 | @ips[@active_ip][0] 46 | end 47 | 48 | def q 49 | coords.q 50 | end 51 | 52 | def r 53 | coords.r 54 | end 55 | 56 | def run 57 | if @grid.size < 1 58 | return 59 | end 60 | loop do 61 | cmd, dbg = @grid.get coords 62 | @debug_tick = @debug_level > 1 || (@debug_level > 0 && dbg) 63 | if @debug_tick 64 | puts "\nTick #{@tick}:" 65 | puts "IPs (! indicates active IP):" 66 | @ips.each_with_index{|ip,i| 67 | puts "#{i == @active_ip ? '!' : ' '} #{i}: #{ip[0]}, #{ip[1]}" 68 | } 69 | puts "Command: #{cmd.inspect}" 70 | end 71 | if cmd[0] == :terminate 72 | puts "Memory: #{@memory.inspect}" if @debug_tick 73 | break 74 | end 75 | process cmd 76 | puts "New direction: #{dir}" if @debug_tick 77 | puts "Memory: #{@memory.inspect}" if @debug_tick 78 | @ips[@active_ip][0] += dir.vec 79 | handle_edges 80 | @active_ip = @new_ip 81 | @tick += 1 82 | break if @max_ticks > -1 && @tick >= @max_ticks 83 | end 84 | 85 | @max_ticks > -1 && @tick >= @max_ticks 86 | end 87 | 88 | private 89 | 90 | def process cmd 91 | opcode, param = *cmd 92 | 93 | case opcode 94 | 95 | # Arithmetic 96 | when :digit 97 | val = @memory.get 98 | if val < 0 99 | @memory.set(val*10 - param) 100 | else 101 | @memory.set(val*10 + param) 102 | end 103 | when :inc 104 | @memory.set(@memory.get+1) 105 | when :dec 106 | @memory.set(@memory.get-1) 107 | when :add 108 | @memory.set(@memory.get_left + @memory.get_right) 109 | when :sub 110 | @memory.set(@memory.get_left - @memory.get_right) 111 | when :mul 112 | @memory.set(@memory.get_left * @memory.get_right) 113 | when :div 114 | @memory.set(@memory.get_left / @memory.get_right) 115 | when :mod 116 | @memory.set(@memory.get_left % @memory.get_right) 117 | when :neg 118 | @memory.set(-@memory.get) 119 | 120 | # Memory manipulation 121 | when :mp_left 122 | @memory.move_left 123 | when :mp_right 124 | @memory.move_right 125 | when :mp_reverse 126 | @memory.reverse 127 | when :mp_rev_left 128 | @memory.reverse 129 | @memory.move_right 130 | @memory.reverse 131 | when :mp_rev_right 132 | @memory.reverse 133 | @memory.move_left 134 | @memory.reverse 135 | when :mp_branch 136 | if @memory.get > 0 137 | @memory.move_right 138 | else 139 | @memory.move_left 140 | end 141 | when :mem_cpy 142 | if @memory.get > 0 143 | @memory.set(@memory.get_right) 144 | else 145 | @memory.set(@memory.get_left) 146 | end 147 | when :mem_set 148 | @memory.set(param) 149 | 150 | 151 | # I/O 152 | when :input_char 153 | byte = read_byte 154 | @memory.set(byte ? byte.ord : -1) 155 | when :output_char 156 | @out_str.print (@memory.get % 256).chr 157 | when :input_int 158 | val = 0 159 | sign = 1 160 | loop do 161 | byte = read_byte 162 | case byte 163 | when '+' 164 | sign = 1 165 | when '-' 166 | sign = -1 167 | when '0'..'9', nil 168 | @next_byte = byte 169 | else 170 | next 171 | end 172 | break 173 | end 174 | 175 | loop do 176 | byte = read_byte 177 | if byte && byte[/\d/] 178 | val = val*10 + byte.to_i 179 | else 180 | @next_byte = byte 181 | break 182 | end 183 | end 184 | 185 | @memory.set(sign*val) 186 | when :output_int 187 | @out_str.print @memory.get 188 | 189 | # Control flow 190 | when :jump 191 | @ips[@active_ip][0] += dir.vec 192 | handle_edges 193 | when :mirror_hori 194 | @ips[@active_ip][1] = dir.reflect_hori 195 | when :mirror_vert 196 | @ips[@active_ip][1] = dir.reflect_vert 197 | when :mirror_diag_up 198 | @ips[@active_ip][1] = dir.reflect_diag_up 199 | when :mirror_diag_down 200 | @ips[@active_ip][1] = dir.reflect_diag_down 201 | when :branch_left 202 | @ips[@active_ip][1] = dir.reflect_branch_left(@memory.get > 0) 203 | when :branch_right 204 | @ips[@active_ip][1] = dir.reflect_branch_right(@memory.get > 0) 205 | when :next_ip 206 | @new_ip = (@active_ip+1) % 6 207 | when :prev_ip 208 | @new_ip = (@active_ip-1) % 6 209 | when :choose_ip 210 | @new_ip = @memory.get % 6 211 | 212 | # Others 213 | when :terminate 214 | raise '[BUG] Received :terminate. This shouldn\'t happen.' 215 | when :nop 216 | # Nop(e) 217 | end 218 | end 219 | 220 | def handle_edges 221 | x = q 222 | z = r 223 | y = -x-z 224 | 225 | abs = [x.abs, y.abs, z.abs] 226 | 227 | if @grid.size == 1 228 | @ips[@active_ip][0] = PointAxial.new(0,0) 229 | elsif abs.max >= @grid.size 230 | # First, determine pivot: if there's only one value at @size, that's the pivot. 231 | # If there's two, if @memory.get > 0, the pivot is the first coordinate in a 232 | # cyclically adjacent pair. Otherwise it's the second one. 233 | # Now undo the last step. 234 | # Finally, to do the wrapping, negate all three values and swap the non-pivot values. 235 | max_indices = abs.each_index.select{|i| abs[i] >= @grid.size} 236 | 237 | case max_indices.size 238 | when 1 239 | pivot = max_indices[0] 240 | when 2 241 | a, b = max_indices 242 | # We want the first index, if we consider the two as a cyclically adjacent pair. 243 | # i.e. 244 | # a b pivot 245 | # 0 1 0 246 | # 1 2 1 247 | # 2 0 2 248 | # 1 0 0 249 | # 2 1 1 250 | # 0 2 2 251 | pivot = (a-b)%3 == 1 ? b : a 252 | # Pick the other one if current cell is non-positive 253 | pivot = (pivot+1)%3 if @memory.get <= 0 254 | end # Can't be 3 255 | 256 | i, j = [0, 1, 2].select{|k| k != pivot} 257 | 258 | @ips[@active_ip][0] -= dir.vec 259 | 260 | x = q 261 | z = r 262 | y = -x-z 263 | 264 | wrapped = [x,y,z].map{|i| -i} 265 | wrapped[i], wrapped[j] = wrapped[j], wrapped[i] 266 | 267 | x, _, z = wrapped 268 | 269 | @ips[@active_ip][0].q = x 270 | @ips[@active_ip][0].r = z 271 | end 272 | end 273 | 274 | def read_byte 275 | result = nil 276 | if @next_byte 277 | result = @next_byte 278 | @next_byte = nil 279 | else 280 | result = @in_str.read(1) 281 | end 282 | result 283 | end 284 | end -------------------------------------------------------------------------------- /hexagony.sublime-project: -------------------------------------------------------------------------------- 1 | { 2 | "folders": 3 | [ 4 | { 5 | "path": "." 6 | } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /interpreter.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # coding: utf-8 3 | 4 | require_relative 'hexagony' 5 | require_relative 'grid' 6 | 7 | case ARGV[0] 8 | when "-d" 9 | debug_level = 1 10 | when "-D" 11 | debug_level = 2 12 | when "-g" 13 | size = ARGV[1].to_i 14 | puts Grid.new(size) 15 | exit 16 | else 17 | debug_level = 0 18 | end 19 | 20 | if debug_level > 0 21 | ARGV.shift 22 | end 23 | 24 | Hexagony.run(ARGF.read, debug_level) 25 | -------------------------------------------------------------------------------- /memory.rb: -------------------------------------------------------------------------------- 1 | # Memory is a pointy-topped hexagonal grid which contains one integer value for 2 | # each edge. That is, the data structure is the line graph of an infinite 3 | # hexagonal grid. Edges are indexed by the axial coordinates of the (westward) 4 | # adjacent hexagon, and a symbol :NE, :E, :SE indicating which of the three 5 | # edges is meant (where the direction is taken from the hexagon to the edge). 6 | # The memory pointer includes another flag which indicates whether the MP 7 | # is currently pointing in the clockwise or counter-clockwise directoin (in 8 | # relation to the hexagon used for indexing). 9 | class Memory 10 | def initialize 11 | @memory = Hash.new { 0 } 12 | @mp = [0, 0, :E] 13 | @cw = false 14 | end 15 | 16 | def reverse 17 | @cw = !@cw 18 | end 19 | 20 | def move_left 21 | *@mp, @cw = left_index 22 | end 23 | 24 | def move_right 25 | *@mp, @cw = right_index 26 | end 27 | 28 | def set value 29 | @memory[@mp] = value 30 | end 31 | 32 | def get 33 | @memory[@mp] 34 | end 35 | 36 | def get_left 37 | *mp_left, cw = left_index 38 | @memory[mp_left] 39 | end 40 | 41 | def get_right 42 | *mp_right, cw = right_index 43 | @memory[mp_right] 44 | end 45 | 46 | def left_index 47 | q, r, e = @mp 48 | cw = @cw 49 | case [e, cw] 50 | when [:NE, false] 51 | r -= 1 52 | e = :SE 53 | cw = true 54 | when [:NE, true] 55 | q += 1 56 | r -= 1 57 | e = :SE 58 | cw = false 59 | when [:E, false] 60 | e = :NE 61 | when [:E, true] 62 | r += 1 63 | e = :NE 64 | when [:SE, false] 65 | e = :E 66 | when [:SE, true] 67 | q -= 1 68 | r += 1 69 | e = :E 70 | end 71 | [q, r, e, cw] 72 | end 73 | 74 | def right_index 75 | q, r, e = @mp 76 | cw = @cw 77 | case [e, cw] 78 | when [:NE, false] 79 | r -= 1 80 | e = :E 81 | when [:NE, true] 82 | e = :E 83 | when [:E, false] 84 | q += 1 85 | r -= 1 86 | e = :SE 87 | when [:E, true] 88 | e = :SE 89 | when [:SE, false] 90 | r += 1 91 | e = :NE 92 | cw = true 93 | when [:SE, true] 94 | q -= 1 95 | r += 1 96 | e = :NE 97 | cw = false 98 | end 99 | [q, r, e, cw] 100 | end 101 | end --------------------------------------------------------------------------------