├── .gitignore ├── LICENSE ├── README.md ├── examples ├── abs.sks ├── bitflip.sks ├── cat.sks ├── double.sks ├── first-number.sks ├── hello-world.sks ├── infinite-loop.sks ├── is-not-zero.sks ├── is-prime.sks ├── is-zero.sks ├── logic-gates │ ├── 0000.sks │ ├── 0001.sks │ ├── 0011.sks │ ├── 0110.sks │ ├── 1100.sks │ ├── 1101.sks │ └── 1111.sks ├── reverse-input.sks └── reversed-subtraction.sks ├── python └── interpreter.py ├── ruby ├── interpreter.rb ├── stack.rb ├── stackcats.rb └── tape.rb └── stack-cats.sublime-project /.gitignore: -------------------------------------------------------------------------------- 1 | *.txt 2 | *.sublime-workspace 3 | mycode 4 | 5 | ################# 6 | ## Eclipse 7 | ################# 8 | 9 | *.pydevproject 10 | .project 11 | .metadata 12 | bin/ 13 | tmp/ 14 | *.tmp 15 | *.bak 16 | *.swp 17 | *~.nib 18 | local.properties 19 | .classpath 20 | .settings/ 21 | .loadpath 22 | 23 | # External tool builders 24 | .externalToolBuilders/ 25 | 26 | # Locally stored "Eclipse launch configurations" 27 | *.launch 28 | 29 | # CDT-specific 30 | .cproject 31 | 32 | # PDT-specific 33 | .buildpath 34 | 35 | 36 | ################# 37 | ## Visual Studio 38 | ################# 39 | 40 | ## Ignore Visual Studio temporary files, build results, and 41 | ## files generated by popular Visual Studio add-ons. 42 | 43 | # User-specific files 44 | *.suo 45 | *.user 46 | *.sln.docstates 47 | 48 | # Build results 49 | 50 | [Dd]ebug/ 51 | [Rr]elease/ 52 | x64/ 53 | build/ 54 | [Bb]in/ 55 | [Oo]bj/ 56 | 57 | # MSTest test Results 58 | [Tt]est[Rr]esult*/ 59 | [Bb]uild[Ll]og.* 60 | 61 | *_i.c 62 | *_p.c 63 | *.ilk 64 | *.meta 65 | *.obj 66 | *.pch 67 | *.pdb 68 | *.pgc 69 | *.pgd 70 | *.rsp 71 | *.sbr 72 | *.tlb 73 | *.tli 74 | *.tlh 75 | *.tmp 76 | *.tmp_proj 77 | *.log 78 | *.vspscc 79 | *.vssscc 80 | .builds 81 | *.pidb 82 | *.log 83 | *.scc 84 | 85 | # Visual C++ cache files 86 | ipch/ 87 | *.aps 88 | *.ncb 89 | *.opensdf 90 | *.sdf 91 | *.cachefile 92 | 93 | # Visual Studio profiler 94 | *.psess 95 | *.vsp 96 | *.vspx 97 | 98 | # Guidance Automation Toolkit 99 | *.gpState 100 | 101 | # ReSharper is a .NET coding add-in 102 | _ReSharper*/ 103 | *.[Rr]e[Ss]harper 104 | 105 | # TeamCity is a build add-in 106 | _TeamCity* 107 | 108 | # DotCover is a Code Coverage Tool 109 | *.dotCover 110 | 111 | # NCrunch 112 | *.ncrunch* 113 | .*crunch*.local.xml 114 | 115 | # Installshield output folder 116 | [Ee]xpress/ 117 | 118 | # DocProject is a documentation generator add-in 119 | DocProject/buildhelp/ 120 | DocProject/Help/*.HxT 121 | DocProject/Help/*.HxC 122 | DocProject/Help/*.hhc 123 | DocProject/Help/*.hhk 124 | DocProject/Help/*.hhp 125 | DocProject/Help/Html2 126 | DocProject/Help/html 127 | 128 | # Click-Once directory 129 | publish/ 130 | 131 | # Publish Web Output 132 | *.Publish.xml 133 | *.pubxml 134 | *.publishproj 135 | 136 | # NuGet Packages Directory 137 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 138 | #packages/ 139 | 140 | # Windows Azure Build Output 141 | csx 142 | *.build.csdef 143 | 144 | # Windows Store app package directory 145 | AppPackages/ 146 | 147 | # Others 148 | sql/ 149 | *.Cache 150 | ClientBin/ 151 | [Ss]tyle[Cc]op.* 152 | ~$* 153 | *~ 154 | *.dbmdl 155 | *.[Pp]ublish.xml 156 | *.pfx 157 | *.publishsettings 158 | 159 | # RIA/Silverlight projects 160 | Generated_Code/ 161 | 162 | # Backup & report files from converting an old project file to a newer 163 | # Visual Studio version. Backup files are not needed, because we have git ;-) 164 | _UpgradeReport_Files/ 165 | Backup*/ 166 | UpgradeLog*.XML 167 | UpgradeLog*.htm 168 | 169 | # SQL Server files 170 | App_Data/*.mdf 171 | App_Data/*.ldf 172 | 173 | ############# 174 | ## Windows detritus 175 | ############# 176 | 177 | # Windows image file caches 178 | Thumbs.db 179 | ehthumbs.db 180 | 181 | # Folder config file 182 | Desktop.ini 183 | 184 | # Recycle Bin used on file shares 185 | $RECYCLE.BIN/ 186 | 187 | # Mac crap 188 | .DS_Store 189 | 190 | 191 | ############# 192 | ## Python 193 | ############# 194 | 195 | *.py[cod] 196 | 197 | # Packages 198 | *.egg 199 | *.egg-info 200 | dist/ 201 | build/ 202 | eggs/ 203 | parts/ 204 | var/ 205 | sdist/ 206 | develop-eggs/ 207 | .installed.cfg 208 | 209 | # Installer logs 210 | pip-log.txt 211 | 212 | # Unit test / coverage reports 213 | .coverage 214 | .tox 215 | 216 | #Translations 217 | *.mo 218 | 219 | #Mr Developer 220 | .mr.developer.cfg 221 | -------------------------------------------------------------------------------- /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 | # Stack Cats 2 | 3 | **Stack Cats** (abbreviated as **SKS**) is a stack-based, [reversible](https://en.wikipedia.org/wiki/Reversible_computing) (esoteric) programming language. It was originally conceived of [for a language-design challenge on Code Golf Stack Exchange](http://codegolf.stackexchange.com/q/61804/8478), but later designed and developed independently of that. 4 | 5 | ## Basics 6 | 7 | Every program in Stack Cats is written on a single line, where each character is a command. Being a reversible programming language means that for every command there is a way to undo it. The language is designed such that any snippet of commands can be undone by *mirroring* it. This means that the string of commands is reversed and all characters that come in symmetric pairs are swapped (`()`, `{}`, `[]`, `<>`, `\/`). For instance, the snippet `>[[(!-)/` undoes the snippet `\(-!)]]<`. Therefore, Stack Cats programs are made up exclusively of characters which are either self-symmetric (in most fonts) like `-_|!:I` or which come in pairs, i.e. `(){}[]<>\/`. Note that from a theoretical standpoint, self-symmetric characters compute [involutions](https://en.wikipedia.org/wiki/Involution_(mathematics)), while symmetric pairs compute [bijections](https://en.wikipedia.org/wiki/Bijection) (and their inverses). 8 | 9 | ## Syntax 10 | 11 | As stated above, Stack Cats programs are written on a single line. However, source files may contain additional lines for comments or command-line instructions. Everything starting at the first newline is ignored by the interpreter. 12 | 13 | Beyond that there are only two syntactic rules for Stack Cats: 14 | 15 | 1. Every valid program must have mirror symmetry. That is, if the entire program is mirrored (see above), it must remain unchanged. This has a few implications. a) Every Stack Cats program computes an involution on the global memory state (since every program is its own inverse). b) Every even-length program computes the identity (i.e. a [cat program](http://esolangs.org/wiki/Cat_program)) provided that it terminates, since the second half undoes the first. c) Every non-trivial program has odd length. If we call the command in the centre of the program `A`, then every such program has the structure `pAq`, where `p` is an arbitrary program and `q` computes its inverse. Note that `A` needs to be one of the self-symmetric characters, so it is itself an involution. Hence, programming in Stack Cats is about finding a function `p` which transforms a very simple involution `A` into the desired program. 16 | 2. Parentheses, `()`, and braces, `{}`, need to be balanced correctly, as they form loops. They can be nested arbitrarily but not interleaved like `({)}`. (`[]` and `<>` can appear individually and don't need be matched.) 17 | 18 | Note that using unknown characters (i.e. any which don't correspond to a command) will result in an error at the start of the program. 19 | 20 | ## Memory model 21 | 22 | Stack Cats operates on an infinite tape of stacks. The tape has a tape head which can be moved and points at the "current" stack. Commands tend to operate locally on or near the tape head. The stacks store arbitrary-precision (signed) integers and contain an implicit, infinite amount of zeros at the bottom. Initially, all stacks but the one where the tape head starts are empty (apart from those zeros). 23 | 24 | Note that any zeros on top of this implicit pool of zeros are not distinguishable from it by any means. Therefore, in the remainder of this document "the bottom of the stack" always refers to the last non-zero value on the stack. 25 | 26 | ## I/O 27 | 28 | To ensure full reversibility, Stack Cats has no I/O commands, as these side-effects cannot be reversed cleanly. Instead, when the program starts, all input (which has to be finite) is read from the standard input stream. A `-1` is pushed on the initial stack, and then all the bytes from the input are pushed, with the first input byte on top and the last input byte at the bottom (just above the `-1`). 29 | 30 | At the end of the program (provided it terminates), the contents of the current stack (pointed at by the tape head) are taken modulo 256 and printed as bytes to the standard output stream. Again, the value on top is used for the first byte and the value at the bottom is used for the last byte. If the value at the very bottom is `-1`, it is ignored. In order to print trailing null bytes you can either put a `-1` below them, or you can use any non-zero multiple of 256 which are also printed as null-bytes. 31 | 32 | ## Execution Options 33 | 34 | Every specification-compliant interpreter should provide the following options for executing Stack Cats programs: 35 | 36 | - For input, read decimal signed integers instead of bytes. If this option is used, the input is scanned for numbers matching the regular expression `[-+]?[0-9]+` and push those instead of byte values. (Still with a `-1` at the bottom.) 37 | - For output, print decimal signed integers instead of bytes. Every integer is followed by a single linefeed (0x0A). (Still, an optional `-1` at the bottom is ignored.) 38 | - Implicitly mirror the source code. Since one half of every valid Stack Cats program is redundant, there should be options to omit either everything in front of or after the centre character. As an example, consider the source code `:>[(!)-`. With one option, this would be implicitly mirrored to the right to give `:>[(!)-(!)]<:`, and with another it would be implicitly mirrored to the left to give `-(!)]<:>[(!)-`. Note that one character isn't mirrored, because that would result in a trivial even-length program. 39 | 40 | ## Commands 41 | 42 | In the following section, "the stack" refers to the stack currently pointed at by the tape head, and "the top" refers to the top value on that stack. 43 | 44 | ### Control Flow 45 | 46 | Remember that `()` and `{}` always have to be balanced correctly. 47 | 48 | - `(`: If the top is zero or negative, control flow continues after the matching `)`. 49 | - `)`: If the top is zero or negative, control flow continues after the matching `(`. 50 | - `{`: Remembers the top. 51 | - `}`: If the top differs from the value remembered at the matching `{`, control flow continues after the matching `{` (without remembering a new value). 52 | 53 | In summary, `()` is a loop which is entered and left only when the top is positive, whereas `{}` is a loop which always iterates at least once and stops when the value from the beginning is seen again at the end of an iteration. 54 | 55 | ### Arithmetic 56 | 57 | - `-`: Negate the top (i.e. multiply by `-1`). 58 | - `!`: Take the bitwise NOT of the top (this is equivalent to incrementing and negating). 59 | - `*`: Toggle the least-significant bit of the top. In other words, compute `x XOR 1`. 60 | - `_`: Pop `a`, pop `b`, push `b`, push `b - a`. 61 | - `^`: Pop `a`, pop `b`, push `b`, push `b XOR a`. 62 | 63 | ### Stack manipulation 64 | 65 | - `:`: Swap the top two elements of the stack. 66 | - `+`: Swap the top and third elements of the stack. 67 | - `=`: Swap the top elements of the two adjacent stacks. 68 | - `|`: Reverse all values on the stack down to (and excluding) the first zero from the top. 69 | - `T`: If the top is non-zero, reverse the entire stack (down to and including the bottommost non-zero value). 70 | 71 | ### Movement and tape manipulation 72 | 73 | - `<`: Move the tape head left one stack. 74 | - `>`: Move the tape head right one stack. 75 | - `[`: Move the tape head left one stack, taking the top with it. 76 | - `]`: Move the tape head right one stack, taking the top with it. 77 | - `I`: If the top is negative, do `[-`, if it is positive, do `]-`, if it is zero, do nothing. 78 | - `/`: Swap the current stack with the stack to the left, and move the tape head left. 79 | - `\`: Swap the current stack with the stack to the right, and move the tape head right. 80 | - `X`: Swap the stacks left and right of the current stack. 81 | 82 | ## Interpreters 83 | 84 | This repository contains two reference implementations, one in Python and one in Ruby. 85 | 86 | ### Ruby 87 | 88 | The Ruby interpreter can be run as follows: 89 | 90 | ruby ./interpreter.rb [options] ./program.sks 91 | 92 | It supports the following options: 93 | 94 | - `-i` use integer input. 95 | - `-o` use integer output. 96 | - `-n` use both integer input and output. (`n` for **n**umeric.) 97 | - `-m` implicitly mirror the source code to the right. 98 | - `-M` instead of executing the program, mirror it to the right and print it. 99 | - `-l` and `-L`, same as `-m` and `-M` but mirror the program to the left. 100 | - `-d` debug level 1: the command `"` can be inserted into the program to print debug information. All `"` are stripped before checking symmetry. 101 | - `-D` debug level 2: print debug information after every command. 102 | 103 | Options can be combined like `-imd`. 104 | 105 | ### Python 3 106 | 107 | The Python 3 interpreter can be run like the Ruby interpreter: 108 | 109 | python3 ./interpreter.py [options] ./program.sks 110 | 111 | It has all the options of the Ruby interpreter, plus two extras: 112 | 113 | - `-h` display options. 114 | - `-t MAX_TICKS` run the program for at most `MAX_TICKS` ticks, erroring on timeout. 115 | -------------------------------------------------------------------------------- /examples/abs.sks: -------------------------------------------------------------------------------- 1 | <{>I<}> -------------------------------------------------------------------------------- /examples/bitflip.sks: -------------------------------------------------------------------------------- 1 | (^[>!*)<*>(*!<]^) 2 | 3 | Takes input as a string of 1s and 0s, e.g. 111011010000 -> 000100101111. -------------------------------------------------------------------------------- /examples/cat.sks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/m-ender/stackcats/7109b1b6aa3953edcc2c1b40ab88f0bbc6dc7143/examples/cat.sks -------------------------------------------------------------------------------- /examples/double.sks: -------------------------------------------------------------------------------- 1 | [_-:^:-_] -------------------------------------------------------------------------------- /examples/first-number.sks: -------------------------------------------------------------------------------- 1 | :!:[X]:!: -------------------------------------------------------------------------------- /examples/hello-world.sks: -------------------------------------------------------------------------------- 1 | (]<*[[>>]<]^+<[>\]_-]<<<]*_-]]^:[_-:^:+<*]>]^:<]:<]]^:[>>[[:_-_-^]<[}]<_!]<_!]<-!*-!^:[:_-_-:[^:]_-:_-:_-:_-_-^:)*-*(:^-_-_:-_:-_:-_[:^]:-_-_:]:^!-*!->[!_>[!_>[{]>[^-_-_:]]<<<}>[!-:^[[\\>]:^[[>:[>:^[<<]]\\>[*>+:^:-_]:^[[-_*[>>>[-_[/<]>+^[>[<<]]*>[) -------------------------------------------------------------------------------- /examples/infinite-loop.sks: -------------------------------------------------------------------------------- 1 | {<}{>} -------------------------------------------------------------------------------- /examples/is-not-zero.sks: -------------------------------------------------------------------------------- 1 | |I| -------------------------------------------------------------------------------- /examples/is-prime.sks: -------------------------------------------------------------------------------- 1 | [<(*>=*(:)*[(>*{[[>[:<[>>_(_-<<(-!>)>(>-)):]<^:>!->}<*)*[^:<)*(>:^]*(*>{<-!<:^>[:((-<)<(>-_)_<<]>:]<]]}*<)]*(:)*=<*)>] 2 | 3 | Assumes the -n flag. 4 | -------------------------------------------------------------------------------- /examples/is-zero.sks: -------------------------------------------------------------------------------- 1 | -|-I:I-|- -------------------------------------------------------------------------------- /examples/logic-gates/0000.sks: -------------------------------------------------------------------------------- 1 | :!!: 2 | 3 | 0000 - FALSE 4 | ============ 5 | 6 | :!!: 7 | :!>X 18 | 19 | This one creates a -1 in a separate stack, uses + to pull two zeroes on top of the -1 then moves 20 | one of those zeroes away. -------------------------------------------------------------------------------- /examples/logic-gates/0001.sks: -------------------------------------------------------------------------------- 1 | [>I=I_I=I<] 2 | 3 | 0001 - AND 4 | ========== 5 | 6 | [>I=I_I=I<] 7 | [>IXI_IXI<] 8 | ][ 9 | ][ 10 | 11 | All of these work similarly, so we'll use the first one for explaining. 12 | 13 | [> moves P one stack left, and I=I either removes the -1 at the bottom of the input stack if Q = 1, 14 | or moves P to the stack on the other side of the input stack if Q = 0. 15 | 16 | Regardless of what just happened, the next _ turns Q into -1, since either -1-0 or 0-1 occurs. I=I 17 | happens again, this time to empty the input stack if the -1's still there, and <] puts P on top if 18 | it wasn't moved previously, otherwise 0. -------------------------------------------------------------------------------- /examples/logic-gates/0011.sks: -------------------------------------------------------------------------------- 1 | :!:[X]:!: 2 | 3 | 0011 - P 4 | ======== 5 | 6 | :!:[X]:!: 7 | :!:]X[:!: 8 | 9 | For both of these, the initial :!: is irrelevant, the middle chars get P onto a new stack, and the 10 | final :!: put a -1 under P. 11 | 12 | +I=I:I=I+ 13 | 14 | The initial +I=I: removes Q and moves P and -1, leaving the stack to the right of the input stack 15 | at the state [P -1 0]. The next I=I does nothing useful, and finally the last + pulls P to the top. 16 | 17 | [X] 18 | [>!!<] 19 | [>!>X!>[ 21 | ]X[ 22 | ]>!!<[ 23 | 24 | These all move P off the top and form a -1 on a new stack. The centre X then switches this new 25 | stack with the input stack, and the second half puts P on top of it. -------------------------------------------------------------------------------- /examples/logic-gates/0110.sks: -------------------------------------------------------------------------------- 1 | ^:]<_I_>[:^ 2 | 3 | 0110 - XOR / NOT EQUALS 4 | ======================= 5 | 6 | The above is the only length 11 solution. 7 | 8 | ^ performs a XOR and :]< removes Q from the input stack. _ results in -1-(P^Q), and I_ moves 9 | this negative value leftward. >[: then moves the -1 from the input stack and puts it under the 10 | -1-(P^Q), and the ^ fixes the top of the stack back to P^Q. -------------------------------------------------------------------------------- /examples/logic-gates/1100.sks: -------------------------------------------------------------------------------- 1 | I^:!:^I 2 | 3 | 1100 - NOT P 4 | ============ 5 | 6 | The above is the only length 7 solution. 7 | 8 | In the (0, Q) case, the initial I does nothing. ^:!:^ then results in 9 | 10 | [-1 Q 0] -> [-1 Q Q] -> [-1 Q Q] -> [-1 Q ~Q] -> [-1 ~Q Q] -> [-1 ~Q -1] 11 | 12 | and the final I pushes the -1 to the left, turning it into a 1. 13 | 14 | In the (1, Q) case, the inital I moves P = 1 to the right, turning it into -1. Then we get: 15 | 16 | [0 -1] -> [0 -1] -> [-1 0] -> [-1 -1] -> [-1 -1] -> [-1 0] 17 | 18 | and the final I does nothing. -------------------------------------------------------------------------------- /examples/logic-gates/1101.sks: -------------------------------------------------------------------------------- 1 | |I|^:!:^|I| 2 | 3 | 1101 - Q IMPLY P / NOT P OR Q 4 | ============================= 5 | 6 | |I|^:!:^|I| 7 | |IT^:!:^TI| 8 | 9 | Both of these work similarly, so we'll use the first for explaining. 10 | 11 | In the (P, Q) = (1, 0) case, the first I moves P off to the right, turning it into -1. The :!:^ 12 | then puts a -1 beneath this and XORs, making the stack [-1 0]. The rest of program then does 13 | nothing, and 0 is output. 14 | 15 | If P = 0, then |I| does nothing. ^:!:^ transforms the input stack from [-1 Q 0] to 16 | 17 | [-1 Q 0] -> [-1 Q Q] -> [-1 Q Q] -> [-1 Q ~Q] -> [-1 ~Q Q] -> [-1 ~Q -1] 18 | 19 | and |I| pulls the -1 at the base, pushing it left whilst turning it into a 1. 20 | 21 | In the (P, Q) = (1, 1) case, the initial | actually reverses, pulling the base -1 to the top. I 22 | pushes this leftward, turning it into a 1, and |^ does nothing. :!:^| essentially creates a -1, 23 | which is once again moved leftward by I, turning it into the 1 which gets output. -------------------------------------------------------------------------------- /examples/logic-gates/1111.sks: -------------------------------------------------------------------------------- 1 | ** 2 | 3 | 1111 - TRUE 4 | =========== 5 | 6 | ** 7 | *>X<* 8 | 9 | These two simply use X to ignore the input, and * to create the 1. 10 | 11 | +I+I+ 12 | 13 | This one manipulates the -1 at the bottom of the input stack to create the 1. -------------------------------------------------------------------------------- /examples/reverse-input.sks: -------------------------------------------------------------------------------- 1 | |[>|<]| -------------------------------------------------------------------------------- /examples/reversed-subtraction.sks: -------------------------------------------------------------------------------- 1 | !:!:_I!I_:!:! 2 | 3 | Takes two integers a, b as input and outputs b-a, e.g. 10 3 -> -7. -------------------------------------------------------------------------------- /python/interpreter.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | from collections import defaultdict, deque 3 | import re 4 | import sys 5 | 6 | 7 | class InvalidCodeException(Exception): 8 | pass 9 | 10 | 11 | class BottomlessStack(): 12 | def __init__(self): 13 | self.stack = deque() 14 | 15 | def push(self, value): 16 | self.stack.append(value) 17 | self.swallow_zeroes() 18 | 19 | def pop(self): 20 | if self.stack: 21 | return self.stack.pop() 22 | 23 | return 0 24 | 25 | def peek(self): 26 | if self.stack: 27 | return self.stack[-1] 28 | 29 | return 0 30 | 31 | def reverse(self): 32 | self.stack.reverse() 33 | self.swallow_zeroes() 34 | 35 | def swallow_zeroes(self): 36 | while self.stack and self.stack[0] == 0: 37 | self.stack.popleft() 38 | 39 | def __len__(self): 40 | return len(self.stack) 41 | 42 | def __iter__(self): 43 | return iter(self.stack) 44 | 45 | def __getitem__(self, index): 46 | return self.stack[index] 47 | 48 | 49 | class Tape(): 50 | def __init__(self): 51 | self.stacks = defaultdict(BottomlessStack) 52 | self.stack_num = 0 53 | self.curr_stack = self.stacks[self.stack_num] 54 | 55 | def push(self, value): 56 | self.curr_stack.push(value) 57 | 58 | def pop(self): 59 | return self.curr_stack.pop() 60 | 61 | def peek(self): 62 | return self.curr_stack.peek() 63 | 64 | def reverse(self): 65 | self.curr_stack.reverse() 66 | 67 | def swallow_zeroes(self): 68 | self.curr_stack.swallow_zeroes() 69 | 70 | def move_by(self, offset): 71 | if self.stack_num in self.stacks and not self.curr_stack: 72 | del self.stacks[self.stack_num] 73 | 74 | self.stack_num += offset 75 | self.curr_stack = self.stacks[self.stack_num] 76 | 77 | def move_left(self): 78 | self.move_by(-1) 79 | 80 | def move_right(self): 81 | self.move_by(1) 82 | 83 | def swap_stacks(self, num1, num2): 84 | self.stacks[num1], self.stacks[num2] = self.stacks[num2], self.stacks[num1] 85 | 86 | if num1 in self.stacks and not self.stacks[num1]: 87 | del self.stacks[num1] 88 | 89 | if num2 in self.stacks and not self.stacks[num2]: 90 | del self.stacks[num2] 91 | 92 | self.curr_stack = self.stacks[self.stack_num] 93 | 94 | def swap_left(self): 95 | self.swap_stacks(self.stack_num-1, self.stack_num) 96 | 97 | def swap_right(self): 98 | self.swap_stacks(self.stack_num+1, self.stack_num) 99 | 100 | def __len__(self): 101 | return len(self.curr_stack) 102 | 103 | def __str__(self): 104 | max_depth = max(map(len, self.stacks.values())) 105 | min_num = min(self.stacks.keys()) 106 | max_num = max(self.stacks.keys()) 107 | 108 | rows = [] 109 | 110 | for i in range(max_depth+3): 111 | rows.append(["..."] if i == max_depth else [" "]) 112 | 113 | for n in range(min_num, max_num+1): 114 | stack = self.stacks[n] if n in self.stacks else [] 115 | width = max(len(str(x)) for x in stack) if stack else 1 116 | 117 | rows[0].append(('v' if self.stack_num == n else ' ').rjust(width)) 118 | rows[-1].append(('^' if self.stack_num == n else ' ').rjust(width)) 119 | rows[-2].append('0'.rjust(width)) 120 | 121 | for i in range(max_depth): 122 | rows[max_depth-i].append((str(stack[i]) if i < len(stack) else '').rjust(width)) 123 | 124 | for i in range(max_depth+3): 125 | rows[i].append("..." if i == max_depth else " ") 126 | 127 | return '\n'.join(' '.join(row).rstrip() for row in rows) 128 | 129 | 130 | class StackCats(): 131 | def __init__(self, code, debug_level=0, mirror_left=False, mirror_right=False, 132 | print_mirrored=False): 133 | code = code.splitlines()[0] 134 | 135 | if mirror_right: 136 | code = code + self.__mirror(code[:-1]) 137 | elif mirror_left: 138 | code = self.__mirror(code[1:]) + code 139 | 140 | # Code potentially minus debug chars if debug options are set. 141 | program = code 142 | self.debug_level = debug_level 143 | 144 | if debug_level > 0: 145 | program = program.replace('"', '') 146 | 147 | if program != self.__mirror(program): 148 | self.error(InvalidCodeException, "program is not symmetric") 149 | 150 | self.code = code 151 | self.loop_targets = {} 152 | index_stack = [] 153 | 154 | for i,c in enumerate(code): 155 | if c not in "(){}-!*_^:+=|T<>[]I/\\X" and (c != '"' or self.debug_level == 0): 156 | self.error(InvalidCodeException, 'invalid character in source code, {}'.format(c)) 157 | 158 | if c in "{(": 159 | index_stack.append((i, c)) 160 | 161 | elif c in "})": 162 | if not index_stack or c != self.__mirror(index_stack[-1][1]): 163 | self.error(InvalidCodeException, "unmatched {}".format(c)) 164 | 165 | start, _ = index_stack.pop() 166 | self.loop_targets[start] = i 167 | self.loop_targets[i] = start 168 | 169 | if index_stack: 170 | self.error(InvalidCodeException, "unmatched {}".format(index_stack[-1][1])) 171 | 172 | if print_mirrored: 173 | print(code, end='') 174 | 175 | 176 | def run(self, input_="", numeric_input=False, numeric_output=False, max_ticks=None): 177 | # Prepare tape, with initial input. 178 | self.tape = Tape() 179 | self.tape.push(-1) 180 | 181 | if numeric_input: 182 | for number in re.findall("[-+]?[0-9]+", input_)[::-1]: 183 | self.tape.push(int(number)) 184 | else: 185 | for c in input_[::-1]: 186 | self.tape.push(ord(c)) 187 | 188 | # Run code. 189 | self.loop_conditions = [] 190 | self.ip = 0 191 | self.tick = 0 192 | self.output = [] 193 | 194 | while self.ip < len(self.code): 195 | if self.debug_level >= 2: 196 | self.print_debug() 197 | 198 | self.interpret(self.code[self.ip]) 199 | self.ip += 1 200 | self.tick += 1 201 | 202 | if max_ticks is not None and self.tick >= max_ticks: 203 | self.error(TimeoutError, "program timed out") 204 | 205 | if self.debug_level >= 2: 206 | self.print_debug() 207 | 208 | # Output, ignoring any -1s at bottom. 209 | while self.tape: 210 | value = self.tape.pop() 211 | 212 | if value != -1 or self.tape: 213 | if numeric_output: 214 | self.output.extend([str(value), '\n']) 215 | else: 216 | self.output.append(chr(value % 256)) 217 | 218 | self.output = ''.join(self.output) 219 | return self.output 220 | 221 | 222 | def interpret(self, instruction): 223 | self.execute_inst(instruction) 224 | self.tape.swallow_zeroes() 225 | 226 | 227 | def execute_inst(self, instruction): 228 | # Symmetric pairs 229 | if instruction == '(': 230 | if self.tape.peek() < 1: 231 | self.ip = self.loop_targets[self.ip] 232 | 233 | elif instruction == ')': 234 | if self.tape.peek() < 1: 235 | self.ip = self.loop_targets[self.ip] 236 | 237 | elif instruction == '/': 238 | self.tape.swap_left() 239 | self.tape.move_left() 240 | 241 | elif instruction == '\\': 242 | self.tape.swap_right() 243 | self.tape.move_right() 244 | 245 | elif instruction == '<': 246 | self.tape.move_left() 247 | 248 | elif instruction == '>': 249 | self.tape.move_right() 250 | 251 | elif instruction == '[': 252 | value = self.tape.pop() 253 | self.tape.move_left() 254 | self.tape.push(value) 255 | 256 | elif instruction == ']': 257 | value = self.tape.pop() 258 | self.tape.move_right() 259 | self.tape.push(value) 260 | 261 | elif instruction == '{': 262 | self.loop_conditions.append(self.tape.peek()) 263 | 264 | elif instruction == '}': 265 | if self.tape.peek() == self.loop_conditions[-1]: 266 | self.loop_conditions.pop() 267 | else: 268 | self.ip = self.loop_targets[self.ip] 269 | 270 | # Self-symmetric characters 271 | # Arithmetic 272 | elif instruction == '!': 273 | self.tape.push(~self.tape.pop()) 274 | 275 | elif instruction == '-': 276 | self.tape.push(-self.tape.pop()) 277 | 278 | elif instruction == '*': 279 | self.tape.push(self.tape.pop() ^ 1) 280 | 281 | elif instruction == '^': 282 | value = self.tape.pop() 283 | self.tape.push(self.tape.peek() ^ value) 284 | 285 | elif instruction == '_': 286 | value = self.tape.pop() 287 | self.tape.push(self.tape.peek() - value) 288 | 289 | elif instruction == ':': 290 | top, bottom = self.tape.pop(), self.tape.pop() 291 | self.tape.push(top) 292 | self.tape.push(bottom) 293 | 294 | elif instruction == '+': 295 | top, middle, bottom = self.tape.pop(), self.tape.pop(), self.tape.pop() 296 | self.tape.push(top) 297 | self.tape.push(middle) 298 | self.tape.push(bottom) 299 | 300 | elif instruction == '|': 301 | values = [] 302 | 303 | while self.tape.peek() != 0: 304 | values.append(self.tape.pop()) 305 | 306 | for v in values: 307 | self.tape.push(v) 308 | 309 | elif instruction == '=': 310 | self.tape.move_left() 311 | x = self.tape.pop() 312 | self.tape.move_right() 313 | self.tape.move_right() 314 | y = self.tape.pop() 315 | self.tape.push(x) 316 | self.tape.move_left() 317 | self.tape.move_left() 318 | self.tape.push(y) 319 | self.tape.move_right() 320 | 321 | elif instruction == 'X': 322 | self.tape.swap_left() 323 | self.tape.swap_right() 324 | self.tape.swap_left() 325 | 326 | elif instruction == 'I': 327 | value = self.tape.pop() 328 | 329 | if value < 0: 330 | self.tape.move_left() 331 | elif value > 0: 332 | self.tape.move_right() 333 | 334 | self.tape.push(-value) 335 | 336 | elif instruction == 'T': 337 | if self.tape.peek() != 0: 338 | self.tape.reverse() 339 | 340 | elif instruction == '"' and self.debug_level >= 1: 341 | self.print_debug() 342 | 343 | def print_debug(self): 344 | print(file=sys.stderr) 345 | print("Tick", self.tick, file=sys.stderr) 346 | print("Tape:", file=sys.stderr) 347 | print(self.tape, file=sys.stderr) 348 | print("Program:", file=sys.stderr) 349 | print(self.code, file=sys.stderr) 350 | print('^'.rjust(self.ip+1), file=sys.stderr) 351 | 352 | def error(self, exception, message): 353 | # Note: the error messages could mirror brackets as well, but that could get confusing. 354 | message = "Error: " + message 355 | raise exception(message + " | " + message[::-1]) 356 | 357 | def __mirror(self, string): 358 | mirror_chars = dict(zip("(){}[]<>\\/", ")(}{][>\\\\/', ')(}{][>\\\\/', ')(}{][> e 51 | $stderr.puts e.message 52 | end 53 | -------------------------------------------------------------------------------- /ruby/stack.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | class Stack 4 | def initialize 5 | @stack = [] 6 | end 7 | 8 | def depth 9 | @stack.size 10 | end 11 | 12 | def empty? 13 | @stack.empty? 14 | end 15 | 16 | def push value 17 | @stack << value unless empty? && value == 0 18 | end 19 | 20 | def pop 21 | empty? ? 0 : @stack.pop 22 | end 23 | 24 | def peek 25 | empty? ? 0 : @stack[-1] 26 | end 27 | 28 | def to_a 29 | @stack 30 | end 31 | 32 | def reverse! 33 | @stack.reverse! 34 | end 35 | end -------------------------------------------------------------------------------- /ruby/stackcats.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | require_relative 'stack' 4 | require_relative 'tape' 5 | 6 | class StackCats 7 | 8 | class ProgramError < Exception; end 9 | 10 | OPERATORS = { 11 | # Asymmetric characters: 12 | # "#$%&,012345679;?@`~" 13 | # Unused self-symmetric characters: 14 | # " '.8AMOUVWYovwx" 15 | # Unused symmetric pairs: 16 | # "bdpq" 17 | 18 | # Symmetric pairs (these need to be each other's inverse): 19 | '(' => :open_sign_loop, 20 | ')' => :close_sign_loop, 21 | '/' => :swap_left, 22 | '\\' => :swap_right, 23 | '<' => :move_left, 24 | '>' => :move_right, 25 | '[' => :push_left, 26 | ']' => :push_right, 27 | '{' => :open_value_loop, 28 | '}' => :close_value_loop, 29 | 30 | # Self-symmetric characters: 31 | '!' => :bit_not, 32 | '"' => :debug, 33 | '*' => :xor_1, 34 | '+' => :swap_third, 35 | '-' => :negate, 36 | ':' => :swap, 37 | '=' => :swap_tops, 38 | 'I' => :cond_push, 39 | 'T' => :cond_reverse, 40 | 'X' => :swap_stacks, 41 | '^' => :xor, 42 | '_' => :sub, 43 | '|' => :reverse, 44 | } 45 | 46 | OPERATORS.default = :invalid 47 | 48 | def self.run(src, num_input=false, num_output=false, debug_level=0, in_str=$stdin, out_str=$stdout, max_ticks=-1) 49 | new(src, num_input, num_output, debug_level, in_str, out_str, max_ticks).run 50 | end 51 | 52 | def initialize(src, num_input=false, num_output=false, debug_level=0, in_str=$stdin, out_str=$stdout, max_ticks=-1) 53 | @num_input = num_input 54 | @num_output = num_output 55 | @debug_level = debug_level 56 | @in_str = in_str 57 | @out_str = out_str 58 | @max_ticks = max_ticks 59 | 60 | @program = src 61 | 62 | preprocess_source 63 | 64 | @ip = 0 65 | @loop_conds = Stack.new 66 | 67 | @tape = Tape.new 68 | 69 | @tick = 0 70 | end 71 | 72 | def preprocess_source 73 | # Validate symmetry 74 | program = @debug_level > 0 ? @program.gsub('"', '') : @program 75 | error "Error: program is not symmetric" if program != program.reverse.tr('(){}[]<>\\\\/', ')(}{][> 1 135 | 136 | op = @program[@ip] 137 | process op 138 | @ip += 1 139 | 140 | @tick += 1 141 | break if @max_ticks > -1 && @tick >= @max_ticks 142 | end 143 | 144 | print_debug_info if @debug_level > 1 145 | 146 | until @tape.empty? 147 | val = @tape.pop 148 | break if val == -1 && @tape.empty? 149 | if @num_output 150 | @out_str.puts val 151 | else 152 | @out_str.print (val % 256).chr 153 | end 154 | end 155 | 156 | @max_ticks > -1 && @tick >= @max_ticks 157 | end 158 | 159 | private 160 | 161 | def print_debug_info 162 | $stderr.puts "\nTick #{@tick}" 163 | $stderr.puts "Tape:" 164 | $stderr.puts @tape.to_s 165 | $stderr.puts "Program:" 166 | $stderr.puts @program 167 | $stderr.puts ' '*@ip + '^' 168 | end 169 | 170 | def process op 171 | case OPERATORS[op] 172 | # Symmetric pairs 173 | when :open_sign_loop 174 | @ip = @loop_targets[@ip] if @tape.peek < 1 175 | when :close_sign_loop 176 | @ip = @loop_targets[@ip] if @tape.peek < 1 177 | when :swap_left 178 | @tape.swap_left 179 | @tape.move_left 180 | when :swap_right 181 | @tape.swap_right 182 | @tape.move_right 183 | when :move_left 184 | @tape.move_left 185 | when :move_right 186 | @tape.move_right 187 | when :push_left 188 | val = @tape.pop 189 | @tape.move_left 190 | @tape.push val 191 | when :push_right 192 | val = @tape.pop 193 | @tape.move_right 194 | @tape.push val 195 | when :open_value_loop 196 | @loop_conds.push @tape.peek 197 | when :close_value_loop 198 | if @tape.peek == @loop_conds.peek 199 | @loop_conds.pop 200 | else 201 | @ip = @loop_targets[@ip] 202 | end 203 | 204 | # Self-symmetric characters 205 | # Arithmetic 206 | when :bit_not 207 | @tape.push ~@tape.pop 208 | when :negate 209 | @tape.push -@tape.pop 210 | when :xor_1 211 | @tape.push (@tape.pop ^ 1) 212 | when :xor 213 | val = @tape.pop 214 | @tape.push (@tape.peek ^ val) 215 | when :sub 216 | val = @tape.pop 217 | @tape.push (@tape.peek - val) 218 | 219 | when :swap 220 | top, bottom = @tape.pop, @tape.pop 221 | @tape.push top 222 | @tape.push bottom 223 | when :swap_third 224 | top, middle, bottom = @tape.pop, @tape.pop, @tape.pop 225 | @tape.push top 226 | @tape.push middle 227 | @tape.push bottom 228 | when :reverse 229 | list = [] 230 | while @tape.peek != 0 231 | list << @tape.pop 232 | end 233 | 234 | list.each { |val| @tape.push val } 235 | when :swap_tops 236 | @tape.move_left 237 | x = @tape.pop 238 | @tape.move_right 239 | @tape.move_right 240 | y = @tape.pop 241 | @tape.push x 242 | @tape.move_left 243 | @tape.move_left 244 | @tape.push y 245 | @tape.move_right 246 | when :swap_stacks 247 | @tape.swap_left 248 | @tape.swap_right 249 | @tape.swap_left 250 | 251 | when :cond_push 252 | val = @tape.pop 253 | if val < 0 254 | @tape.move_left 255 | elsif val > 0 256 | @tape.move_right 257 | end 258 | @tape.push -val 259 | when :cond_reverse 260 | @tape.reverse! if @tape.peek != 0 261 | 262 | when :debug 263 | print_debug_info 264 | end 265 | end 266 | 267 | def read_byte 268 | return nil if STDIN.tty? 269 | result = nil 270 | if @next_byte 271 | result = @next_byte 272 | @next_byte = nil 273 | else 274 | result = @in_str.read(1) 275 | # result = @in_str.read(1) while result =~ /\r|\n/ 276 | end 277 | result 278 | end 279 | 280 | def read_int 281 | val = 0 282 | sign = 1 283 | eof = true 284 | loop do 285 | byte = read_byte 286 | case byte 287 | when '+' 288 | sign = 1 289 | when '-' 290 | sign = -1 291 | when '0'..'9', nil 292 | @next_byte = byte 293 | else 294 | next 295 | end 296 | break 297 | end 298 | 299 | loop do 300 | byte = read_byte 301 | if byte && byte[/\d/] 302 | val = val*10 + byte.to_i 303 | eof = false 304 | else 305 | @next_byte = byte 306 | break 307 | end 308 | end 309 | 310 | eof ? nil : val*sign 311 | end 312 | 313 | def error msg 314 | raise msg + " | " + msg.reverse 315 | end 316 | end -------------------------------------------------------------------------------- /ruby/tape.rb: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | require_relative 'stack' 4 | 5 | class Tape 6 | def initialize 7 | @pos = 0 8 | @tape = [Stack.new] 9 | end 10 | 11 | def stack 12 | @tape[@pos] 13 | end 14 | 15 | def peek 16 | stack.peek 17 | end 18 | 19 | def push value 20 | stack.push value 21 | end 22 | 23 | def pop 24 | stack.pop 25 | end 26 | 27 | def empty? 28 | stack.empty? 29 | end 30 | 31 | def reverse! 32 | stack.reverse! 33 | end 34 | 35 | def move_left 36 | if @pos == 0 37 | @tape.unshift Stack.new 38 | else 39 | @pos -= 1 40 | end 41 | @tape.pop if @tape[-1].empty? 42 | end 43 | 44 | def move_right 45 | if @tape[0].empty? 46 | @tape.shift 47 | else 48 | @pos += 1 49 | end 50 | @tape << Stack.new if @pos == @tape.size 51 | end 52 | 53 | def swap_left 54 | if @pos == 0 55 | @tape.unshift Stack.new 56 | @pos += 1 57 | end 58 | @tape[@pos], @tape[@pos-1] = @tape[@pos-1], @tape[@pos] 59 | if @tape[0].empty? 60 | @tape.shift 61 | @pos -= 1 62 | end 63 | end 64 | 65 | def swap_right 66 | @tape << Stack.new if @pos == @tape.size-1 67 | @tape[@pos], @tape[@pos+1] = @tape[@pos+1], @tape[@pos] 68 | @tape.pop if @tape[-1].empty? 69 | end 70 | 71 | def to_s 72 | max_depth = @tape.map(&:depth).max 73 | widths = @tape.map do |st| 74 | (st.to_a.map{ |val| val.to_s.size } + [1]).max 75 | end 76 | tape_head = ' '*(3 + widths[0..@pos].reduce(:+) + @pos) + 'v' 77 | ([tape_head] + max_depth.times.map do |y| 78 | pos = max_depth - y - 1 79 | surroundings = pos == 0 ? '...' : ' ' 80 | ([surroundings] + @tape.each_with_index.map do |st, i| 81 | list = st.to_a 82 | if pos < list.size 83 | list[pos].to_s.rjust(widths[i]) 84 | else 85 | ' '*widths[i] 86 | end 87 | end + [surroundings]) * ' ' 88 | end + [' ' + widths.map{|w|'0'.rjust(w)} *' ', tape_head.tr('v','^')]) * $/ 89 | end 90 | end -------------------------------------------------------------------------------- /stack-cats.sublime-project: -------------------------------------------------------------------------------- 1 | { 2 | "folders": 3 | [ 4 | { 5 | "path": "." 6 | } 7 | ] 8 | } 9 | --------------------------------------------------------------------------------