diff --git a/brainfuck-to-c-translator/solution.py b/brainfuck-to-c-translator/solution.py new file mode 100644 index 0000000..64ce12c --- /dev/null +++ b/brainfuck-to-c-translator/solution.py @@ -0,0 +1,106 @@ +# https://www.codewars.com/kata/58924f2ca8c628f21a0001a1/ +from enum import Enum, auto +from dataclasses import dataclass + + +class OpKind(Enum): + LEFT = '<' + RIGHT = '>' + INC = '+' + DEC = '-' + INPT = ',' + OUTP = '.' + JMPZ = '[' + JMPNZ = ']' + + +@dataclass +class Opcode: + kind: OpKind + operand: int = 1 + + +def stream_tokens(code) -> Opcode: + for c in code: + try: + yield Opcode(OpKind(c)) + except ValueError: + continue + + +def optimize(tokens): + new_tokens = opti_collapse_tokens(tokens) + if len(new_tokens) <= 1: + return new_tokens + + + tokens = otpi_cancel_opposing(new_tokens) + while len(new_tokens := otpi_cancel_opposing(tokens)) != len(tokens): + tokens = new_tokens + + return tokens + + +def opti_collapse_tokens(tokens): + # collapse consecutive identical tokens + new_tokens = [] + for next_token in tokens: + if new_tokens == []: # init stack + new_tokens.append(next_token) + continue + current_token = new_tokens.pop() + if current_token.kind == next_token.kind and str(current_token.kind.value) in "<>+-": + current_token.operand += 1 + new_tokens.append(current_token) + else: + new_tokens.append(current_token) + new_tokens.append(next_token) + return new_tokens + + +def otpi_cancel_opposing(tokens): + # cancel opposing adjacent tokens + new_tokens = [] + for next_token in tokens: + if new_tokens == []: # init stack + new_tokens.append(next_token) + continue + current_token = new_tokens.pop() + if (current_token.kind.value + next_token.kind.value in ("+-", "-+", "<>", "><", "[]") + and current_token.operand == next_token.operand): + continue + else: + new_tokens.append(current_token) + new_tokens.append(next_token) + return new_tokens + + +def generate_code(tokens): + out = [] + nest = 0 + # code generation + for token in tokens: + match token.kind: + case OpKind.LEFT: out.append(f"{' ' * nest * 2}p -= {token.operand};\n") + case OpKind.RIGHT: out.append(f"{' ' * nest * 2}p += {token.operand};\n") + case OpKind.INC: out.append(f"{' ' * nest * 2}*p += {token.operand};\n") + case OpKind.DEC: out.append(f"{' ' * nest * 2}*p -= {token.operand};\n") + case OpKind.INPT: out.append(f"{' ' * nest * 2}*p = getchar();\n") + case OpKind.OUTP: out.append(f"{' ' * nest * 2}putchar(*p);\n") + case OpKind.JMPZ: out.append(f"{' ' * nest * 2}" + "if (*p) do {\n");nest += 1 + case OpKind.JMPNZ: nest -= 1;out.append(f"{' ' * nest * 2}" + "} while (*p);\n") + + if nest < 0: + return "Error!" + + if nest != 0: + return "Error!" + + return "".join(out) + + +def brainfuck_to_c(source_code): + s = stream_tokens(source_code) + tokens = optimize(s) + code = generate_code(tokens) + return code diff --git a/brainfuck-to-c-translator/tests.py b/brainfuck-to-c-translator/tests.py new file mode 100644 index 0000000..a83e5f0 --- /dev/null +++ b/brainfuck-to-c-translator/tests.py @@ -0,0 +1,32 @@ +import codewars_test as test +from solution import brainfuck_to_c + + +def testing(code, expected): + result = brainfuck_to_c(code) + test.assert_equals(result, expected) + +test.describe("general tests") + +test.it("thib") +testing("-+-+", "") +testing("<>", "") +#testing(">>><<<<", "<") + +test.it("basic") +testing("+-", "") +testing("++++", "*p += 4;\n") +testing("----", "*p -= 4;\n") + +testing(">>>>", "p += 4;\n"); +testing("<<<<", "p -= 4;\n"); + +testing(".", "putchar(*p);\n"); +testing(",", "*p = getchar();\n"); + +testing("[[[]]", "Error!"); + +testing("[][]", ""); + +testing("[.]", "if (*p) do {\n putchar(*p);\n} while (*p);\n"); +