Skip to content

Commit 862555f

Browse files
Merge pull request #470 from mav8557/brainfuck
Add Brainfuck interpreter
2 parents 5621be2 + ce7f234 commit 862555f

File tree

3 files changed

+180
-0
lines changed

3 files changed

+180
-0
lines changed

ciphey/basemods/Decoders/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,5 @@
1919
multi_tap,
2020
url,
2121
tap_code,
22+
brainfuck,
2223
)
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
from typing import Optional, Dict, List, Tuple
2+
3+
from ciphey.iface import Config, ParamSpec, T, U, Decoder, registry, WordList
4+
5+
from loguru import logger
6+
7+
import re
8+
9+
import time
10+
11+
12+
@registry.register
13+
class Brainfuck(Decoder[str, str]):
14+
def decode(self, ctext: T) -> Optional[U]:
15+
"""
16+
Takes a ciphertext and treats it as a Brainfuck program,
17+
interpreting it and saving the output as a string to return.
18+
19+
Brainfuck is a very simple, Turing-complete esoteric language.
20+
Below is a simplified interpreter that attempts to check whether a
21+
given ciphertext is a brainfuck program that would output a string.
22+
23+
A program that can be "decoded" like this is one that:
24+
* Does not require user input ("," instruction)
25+
* Includes at least one putchar instruction (".")
26+
* Does not contain anything but the main 7 instructions,
27+
(excluding ",") and whitespace
28+
29+
Details:
30+
* This implementation wraps the memory pointer for ">" and "<"
31+
* It is time-limited to 60 seconds, to prevent hangups
32+
* The program starts with 100 memory cells, chosen arbitrarily
33+
"""
34+
35+
logger.trace("Attempting brainfuck")
36+
37+
result = ""
38+
memory = [0] * 100
39+
codeptr, memptr = 0, 0 # Instruction pointer and stack pointer
40+
timelimit = 60 # The timeout in seconds
41+
42+
bracemap, isbf = self.bracemap_and_check(ctext)
43+
44+
# If it doesn't appear to be valid brainfuck code
45+
if not isbf:
46+
logger.trace("Failed to interpret brainfuck due to invalid characters")
47+
return None
48+
49+
# Get start time
50+
start = time.time()
51+
52+
while codeptr < len(ctext):
53+
54+
current = time.time()
55+
56+
# Return none if we've been running for over a minute
57+
if current - start > timelimit:
58+
logger.trace("Failed to interpret brainfuck due to timing out")
59+
return None
60+
61+
cmd = ctext[codeptr]
62+
63+
if cmd == "+":
64+
if memory[memptr] < 255:
65+
memory[memptr] = memory[memptr] + 1
66+
else:
67+
memory[memptr] = 0
68+
69+
elif cmd == "-":
70+
if memory[memptr] > 0:
71+
memory[memptr] = memory[memptr] - 1
72+
else:
73+
memory[memptr] = 255
74+
75+
elif cmd == ">":
76+
if memptr == len(memory) - 1:
77+
memory.append(0)
78+
memptr += 1
79+
80+
elif cmd == "<":
81+
if memptr == 0:
82+
memptr = len(memory) - 1
83+
else:
84+
memptr -= 1
85+
86+
# If we're at the beginning of the loop and the memory is 0, exit the loop
87+
elif cmd == "[" and memory[memptr] == 0:
88+
codeptr = bracemap[codeptr]
89+
90+
# If we're at the end of the loop and the memory is >0, jmp to the beginning of the loop
91+
elif cmd == "]" and memory[memptr]:
92+
codeptr = bracemap[codeptr]
93+
94+
# Store the output as a string instead of printing it out
95+
elif cmd == ".":
96+
result += chr(memory[memptr])
97+
98+
codeptr += 1
99+
100+
logger.debug(f"Brainfuck successful, returning '{result}'")
101+
return result
102+
103+
def bracemap_and_check(self, program: str) -> Tuple[Optional[Dict], bool]:
104+
"""
105+
Create a bracemap of brackets in the program, to compute jmps.
106+
Maps open -> close brackets as well as close -> open brackets.
107+
108+
Also returns True if the program is valid Brainfuck code. If False, we
109+
won't even try to run it.
110+
"""
111+
112+
open_stack = []
113+
bracemap = dict()
114+
legal_instructions = {"+", "-", ">", "<", "[", "]", "."}
115+
legal_count = 0
116+
117+
# If the program actually outputs anything (contains ".")
118+
prints = False
119+
120+
for idx, instruction in enumerate(program):
121+
# If instruction is brainfuck (without input) or whitespace, it counts
122+
if instruction in legal_instructions or re.match(r"\s", instruction):
123+
legal_count += 1
124+
125+
if not prints and instruction == ".":
126+
# If there are no "." instructions then this program will not output anything
127+
prints = True
128+
129+
elif instruction == "[":
130+
open_stack.append(idx)
131+
132+
elif instruction == "]":
133+
try:
134+
opbracket = open_stack.pop()
135+
bracemap[opbracket] = idx
136+
bracemap[idx] = opbracket
137+
except IndexError:
138+
# Mismatched braces, not a valid program
139+
# Closing braces > opening braces
140+
return (None, False)
141+
142+
# 1. All characters are instructions or whitespace
143+
# 2. There are no extra open braces
144+
# 3. There is at least one character to be "printed"
145+
# (result is >=1 in length)
146+
is_brainfuck = legal_count == len(program) and len(open_stack) == 0 and prints
147+
148+
return bracemap, is_brainfuck
149+
150+
@staticmethod
151+
def priority() -> float:
152+
# Not uncommon, but not very common either. It's also slow.
153+
return 0.08
154+
155+
def __init__(self, config: Config):
156+
super().__init__(config)
157+
self.ALPHABET = config.get_resource(self._params()["dict"], WordList)
158+
159+
@staticmethod
160+
def getParams() -> Optional[Dict[str, ParamSpec]]:
161+
return {
162+
"dict": ParamSpec(
163+
desc="Brainfuck alphabet (default English)",
164+
req=False,
165+
default="cipheydists::list::englishAlphabet",
166+
)
167+
}
168+
169+
@staticmethod
170+
def getTarget() -> str:
171+
return "brainfuck"

tests/test_main.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,3 +226,11 @@ def test_brandon():
226226
"R hvv blf tzgsvi yvuliv nv...sfmtib...gviirurvw... Xofgxsrmt blfi yzyvh gl blfi yivzhg. Vnkvili Vnsbi srh nzixsvw srh ovtrlmh rmgl lfi ozmwh... Ozrw hrvtv gl vevib uligivhh uiln sviv gl gsv Yofv Nlfmgzrmh. Izyrw zmw izevmlfh, sv yrgvh zmw yrgvh zdzb. Nvm lu gsv Mligs, blf hgzmw zg gsv kivxrkrxv. Blfi prmth szev uzrovw blf, hl mld blf gfim gl gsv tlwh! Zmw bvg blf wl mlg kovzw? Blf wl mlg pmvvo gl wfhg blfi svzwh drgs zhs? Rmhgvzw blf dzro, Dsb szev gsv tlwh ulihzpvm fh? Dv nfhg ollp rmgl gsv girzoh dv uzrovw olmt ztl! Rm z grnv kzhhvw, lfi dliow rmgvigdrmvw drgs zmlgsvi gsilfts zm fksvzezo hxslozih xzoo gsv Xlmqfmxgrlm lu gsv Hksvivh... Gsv tlwh zooldvw fmslob ulixvh gl hork rmgl lfi wlnzrm. Gsv luuhkirmt lu gszg xzgzxobhn dzh gsv mvuvirlfh ulixv xzoovw nztrx... Bvg dv wrw mlg yzmrhs rg, rmhgvzw hgfwbrmt gsv erov zixzmv uli lfi kldvi zmw dvzogs! Zmw gsv nlmhgvih zg lfi wlli...gsv fmslob ivorxgh lu gsrh Xlmqfmxgrlm? ...gsv gilooh...gsv xlikhv vzgvih...gsv dvivdloevh? Wrw dv izrhv lfi hdliwh ztzrmhg gsvn? Li szev dv ozrw gsrh yfiwvm lm lgsvih? Lm hl-xzoovw drgxsvih? Hgizb xsrowivm gzftsg gsv dzbh lu ulfo hlixvib, gsvri ylwrvh nfgzgvw gsilfts yozhksvnlfh irgfzo. Hvmg gl urtsg nlmhgvih gslfts gsvb xlfow mlg wrhgrmtfrhs tllw uiln vero. Gsv uorxpvi lu sfnzmrgb olmt vcgrmtfrhsvw drgsrm gsvn. Bvh, gsvri mfnyvih szev wdrmwovw gsilfts gsv bvzih. Yfg z uvd hgroo ilzn lfi ozmwh, luuvirmt gsvri yollwb dlip uli xlrm. Gl gsrh wzb gsvb hsznv fh drgs gsvri evib vcrhgvmxv! Gsv Mligs yovvwh, uolttvw yb dzi. Gsv yzggovh ziv gsv tlwh' dsrk, xszhgrhvnvmg uli lfi hrmh! Zmw ovg fh mlg ulitvg gsv gviilih, gsv hxlfitvh uiln yvblmw lfi dliow! Gsv Drow Sfmg irwvh gsv hpb drgs vevib ufoo nllm! Gsv wzip izrwvih zywfxg lfi xsrowivm rmgl ozmwh fmpmldm! Hlnv hzb gsvb svizow z hvxlmw Xlmqfmxgrlm! Xzm dv xszig z xlfihv yzxp rmgl gsv ortsg? Droo dv urmw gsv hgivmtgs gl yzmrhs gsv nztvh uiln lfi prmtwlnh? Fmrgv zilfmw gsv dzings lu gsv Vgvimzo Uriv? Mrts rh gsv Grnv lu gsv Hdliw zmw gsv Zcv! Mlmv droo urtsg gsrh dzi rm lfi hgvzw! Mrts rh gsv Grnv lu Nzwmvhh zmw Wrhwzrm!"
227227
)
228228
assert True
229+
230+
def test_brainfuck():
231+
res = decrypt(
232+
Config().library_default().complete_config(),
233+
"+[+++++++>+<]>-.-[+>-----<]>++.+++++++..+++.+[+>++<]>.[++>+<]>---.--[+++>-<]>.-[+>++++<]>.[++>+<]>--.-[+++>++<]>-.+[-->---<]>.--------.[+++++>+<]>+.-[+++>--<]>-.++++++++++.---[+>++<]>.[+++>-<]>++.+++..[+++++>+<]>+.[+++>-<]>+.+[-->---<]>+.----------.-[+++>-<]>-.-[+++>+<]>--.-[+>----<]>.++[+++>--<]>.---.++.------.[+++++>+<]>+.+[+>---<]>+.+++++++++++.--------.-[+++>-<]>--.[+++>-<]>+.+[-->---<]>+.----------.-[+++>-<]>-.[+++>-<]>+.-[-->---<]>..----.-------.[+++++>+<]>+.[+++>-<]>+.+[-->---<]>+.----------.-[+++>-<]>-.[++>+<]>++++.--.-------------.."
234+
)
235+
assert res == answer_str
236+

0 commit comments

Comments
 (0)