@@ 1,162 @@
+"""
+3 + 3
+3 + 3 + 3
+
+# takes values
+# 1. load numbers into bf
+# 2. addition, subtraction
+# 3. printing
+
+[3, 3, proc]
+proc that takes 0th and 1th and adds them and stores in 0th
+"""
+
+from textwrap import dedent
+from typing import Literal, Union
+
+Operator = Literal["+", "-", "*", "/"]
+
+Expr = Union[int, tuple[Operator, "Expr", "Expr"]]
+
+
+def compile(expr: Expr) -> str:
+ """Compile an expression to a piece of code that produces the value of
+ that expression at the current data ptr.
+
+ Additional constraints:
+ - That code must produce that value regardless of the starting value of the
+ current byte.
+ - We must not mutate any data to the left of the data ptr while computing
+ the result.
+ """
+ match expr:
+ case int(num):
+ zero_current = "[-]"
+ insert_num = "+" * num
+ return "".join([zero_current, insert_num])
+ case ("+", left, right):
+ left = compile(left)
+ right = compile(right)
+ return dedent(
+ f"""
+ (add)
+ left: {left}
+ right: >{right}
+ [
+ -<+>
+ ]<
+ """
+ )
+ case ("-", left, right):
+ left = compile(left)
+ right = compile(right)
+ return dedent(
+ f"""
+ (subtract)
+ left: {left}
+ right: >{right}
+ [
+ -<->
+ ]<
+ """
+ )
+ case ("*", left, right):
+ left = compile(left)
+ right = compile(right)
+ return dedent(
+ f"""
+ [-] State after: | *0 |
+ >{right} State after: | 0 | *right |
+ [
+ >{left} State after: | result so far | right | *left |
+ [
+ -<<+>> Add the value of left back into the original data ptr
+ ]
+ < State after: | result so far | *right | ? |
+ -
+ ]< Done
+ """
+ )
+ case ("/", left, right):
+ left = compile(left)
+ right = compile(right)
+ return dedent(
+ f"""
+ [-] Clear result
+ >{left}
+ [ Loop on left being nonzero
+ >{right}
+ [ Loop on right being nonzero
+ >[-]+< Set bit to 1 and move to right
+ -<- Decr right and left
+ [ Loop on left being nonzero
+ >>[-] Set bit to zero
+ <<- Move to left and decr
+ >>>+ Move to left spare and incr
+ <<< Back to left
+ ]
+
+ >>> Move to left spare
+ [
+ -<<<+>>> Empty spare and put it back in left
+ ] On left spare
+ < Move to bit
+ [ Loop on bit being nonzero
+ [-] Clear the bit
+ <[ Loop on right being nonzero
+ <<->> Decrement the result
+ [-] Clear right
+ ]
+ > Move to bit
+ ]
+ <
+ ]
+ <<+> Incr result and move to left
+ ]
+ < Move to result
+ """
+ )
+ case ("%", left, right):
+ rewrite = ("-", left, ("*", right, ("/", left, right)))
+ return compile(rewrite)
+ case ("min", left, right):
+ left = compile(left)
+ right = compile(right)
+ return dedent(
+ f"""
+ 0, 0, 1, 2, D
+ _, _, L, R, _
+
+ If we hit a 0 in left, move to result, produce left and then move
+ one to the right (which is always 0).
+ """
+ )
+ case ("**", left, right):
+ raise NotImplementedError()
+ case _:
+ raise ValueError("Invalid expression")
+
+
+"""
+5 / 15
+counter = 0
+[
+take 15 from 5 and increment counter
+]
+once we've taken the first 5 from 5, stop decrementing
+"""
+
+
+def w(dest: str, contents: str) -> None:
+ with open(dest, "w") as f:
+ f.write(contents)
+
+
+if __name__ == "__main__":
+ program: Expr = ("+", ("-", ("+", 60, 5), 2), 2)
+ program2: Expr = ("+", ("+", 1, 2), 3)
+ program3: Expr = ("*", 33, 2)
+ program4: Expr = ("/", 1, 4)
+ source = compile(program4) + "."
+ print(source)
+ w("65.bf", source)
@@ 1,38 @@
+import subprocess
+from tempfile import NamedTemporaryFile
+
+import pytest
+from bfcomp import compile, Expr
+
+
+def run_program(program: Expr) -> bytes:
+ bf = compile(program) + "."
+ with NamedTemporaryFile() as file:
+ file.write(bf.encode("utf-8"))
+ file.flush()
+ return subprocess.check_output(["./main", file.name], timeout=0.1)
+
+
+@pytest.mark.parametrize(
+ "test_input,expected",
+ [
+ (65, 65),
+ (("+", 60, 5), 65),
+ (("-", 70, 5), 65),
+ (("*", 13, 5), 65),
+ (("*", ("+", 7, 6), 5), 65),
+ (("*", ("-", 150, 145), ("+", 5, 5)), 50),
+ (("/", 100, ("*", ("-", 150, 145), ("+", 5, 5))), 2),
+ (("/", 1, 4), 0),
+ (("/", 120, 33), 3),
+ (("/", 1, 1), 1),
+ (("/", 0, 50), 0),
+ (("%", 36, 10), 6),
+ (("%", 1, 1), 0),
+ (("%", 73, 9), 1),
+ (("min", 20, 10), 10),
+ (("min", 10, 20), 10),
+ ],
+)
+def test_compile(test_input: Expr, expected: int) -> None:
+ assert run_program(test_input) == bytes([expected])