Skip to content

Commit

Permalink
Merge pull request #470 from mav8557/brainfuck
Browse files Browse the repository at this point in the history
Add Brainfuck interpreter
  • Loading branch information
SkeletalDemise committed Oct 3, 2020
2 parents 5621be2 + ce7f234 commit 862555f
Show file tree
Hide file tree
Showing 3 changed files with 180 additions and 0 deletions.
1 change: 1 addition & 0 deletions ciphey/basemods/Decoders/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,5 @@
multi_tap,
url,
tap_code,
brainfuck,
)
171 changes: 171 additions & 0 deletions ciphey/basemods/Decoders/brainfuck.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
from typing import Optional, Dict, List, Tuple

from ciphey.iface import Config, ParamSpec, T, U, Decoder, registry, WordList

from loguru import logger

import re

import time


@registry.register
class Brainfuck(Decoder[str, str]):
def decode(self, ctext: T) -> Optional[U]:
"""
Takes a ciphertext and treats it as a Brainfuck program,
interpreting it and saving the output as a string to return.
Brainfuck is a very simple, Turing-complete esoteric language.
Below is a simplified interpreter that attempts to check whether a
given ciphertext is a brainfuck program that would output a string.
A program that can be "decoded" like this is one that:
* Does not require user input ("," instruction)
* Includes at least one putchar instruction (".")
* Does not contain anything but the main 7 instructions,
(excluding ",") and whitespace
Details:
* This implementation wraps the memory pointer for ">" and "<"
* It is time-limited to 60 seconds, to prevent hangups
* The program starts with 100 memory cells, chosen arbitrarily
"""

logger.trace("Attempting brainfuck")

result = ""
memory = [0] * 100
codeptr, memptr = 0, 0 # Instruction pointer and stack pointer
timelimit = 60 # The timeout in seconds

bracemap, isbf = self.bracemap_and_check(ctext)

# If it doesn't appear to be valid brainfuck code
if not isbf:
logger.trace("Failed to interpret brainfuck due to invalid characters")
return None

# Get start time
start = time.time()

while codeptr < len(ctext):

current = time.time()

# Return none if we've been running for over a minute
if current - start > timelimit:
logger.trace("Failed to interpret brainfuck due to timing out")
return None

cmd = ctext[codeptr]

if cmd == "+":
if memory[memptr] < 255:
memory[memptr] = memory[memptr] + 1
else:
memory[memptr] = 0

elif cmd == "-":
if memory[memptr] > 0:
memory[memptr] = memory[memptr] - 1
else:
memory[memptr] = 255

elif cmd == ">":
if memptr == len(memory) - 1:
memory.append(0)
memptr += 1

elif cmd == "<":
if memptr == 0:
memptr = len(memory) - 1
else:
memptr -= 1

# If we're at the beginning of the loop and the memory is 0, exit the loop
elif cmd == "[" and memory[memptr] == 0:
codeptr = bracemap[codeptr]

# If we're at the end of the loop and the memory is >0, jmp to the beginning of the loop
elif cmd == "]" and memory[memptr]:
codeptr = bracemap[codeptr]

# Store the output as a string instead of printing it out
elif cmd == ".":
result += chr(memory[memptr])

codeptr += 1

logger.debug(f"Brainfuck successful, returning '{result}'")
return result

def bracemap_and_check(self, program: str) -> Tuple[Optional[Dict], bool]:
"""
Create a bracemap of brackets in the program, to compute jmps.
Maps open -> close brackets as well as close -> open brackets.
Also returns True if the program is valid Brainfuck code. If False, we
won't even try to run it.
"""

open_stack = []
bracemap = dict()
legal_instructions = {"+", "-", ">", "<", "[", "]", "."}
legal_count = 0

# If the program actually outputs anything (contains ".")
prints = False

for idx, instruction in enumerate(program):
# If instruction is brainfuck (without input) or whitespace, it counts
if instruction in legal_instructions or re.match(r"\s", instruction):
legal_count += 1

if not prints and instruction == ".":
# If there are no "." instructions then this program will not output anything
prints = True

elif instruction == "[":
open_stack.append(idx)

elif instruction == "]":
try:
opbracket = open_stack.pop()
bracemap[opbracket] = idx
bracemap[idx] = opbracket
except IndexError:
# Mismatched braces, not a valid program
# Closing braces > opening braces
return (None, False)

# 1. All characters are instructions or whitespace
# 2. There are no extra open braces
# 3. There is at least one character to be "printed"
# (result is >=1 in length)
is_brainfuck = legal_count == len(program) and len(open_stack) == 0 and prints

return bracemap, is_brainfuck

@staticmethod
def priority() -> float:
# Not uncommon, but not very common either. It's also slow.
return 0.08

def __init__(self, config: Config):
super().__init__(config)
self.ALPHABET = config.get_resource(self._params()["dict"], WordList)

@staticmethod
def getParams() -> Optional[Dict[str, ParamSpec]]:
return {
"dict": ParamSpec(
desc="Brainfuck alphabet (default English)",
req=False,
default="cipheydists::list::englishAlphabet",
)
}

@staticmethod
def getTarget() -> str:
return "brainfuck"
8 changes: 8 additions & 0 deletions tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,3 +226,11 @@ def test_brandon():
"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!"
)
assert True

def test_brainfuck():
res = decrypt(
Config().library_default().complete_config(),
"+[+++++++>+<]>-.-[+>-----<]>++.+++++++..+++.+[+>++<]>.[++>+<]>---.--[+++>-<]>.-[+>++++<]>.[++>+<]>--.-[+++>++<]>-.+[-->---<]>.--------.[+++++>+<]>+.-[+++>--<]>-.++++++++++.---[+>++<]>.[+++>-<]>++.+++..[+++++>+<]>+.[+++>-<]>+.+[-->---<]>+.----------.-[+++>-<]>-.-[+++>+<]>--.-[+>----<]>.++[+++>--<]>.---.++.------.[+++++>+<]>+.+[+>---<]>+.+++++++++++.--------.-[+++>-<]>--.[+++>-<]>+.+[-->---<]>+.----------.-[+++>-<]>-.[+++>-<]>+.-[-->---<]>..----.-------.[+++++>+<]>+.[+++>-<]>+.+[-->---<]>+.----------.-[+++>-<]>-.[++>+<]>++++.--.-------------.."
)
assert res == answer_str

0 comments on commit 862555f

Please sign in to comment.