~sgeisenh/bfcomp

ad187368e2be82fc0ae24cdc219e64ee05ead604 — Samuel Eisenhandler 1 year, 6 months ago main
initial commit
5 files changed, 235 insertions(+), 0 deletions(-)

A .gitignore
A 65.bf
A bfcomp.py
A bfcomp_test.py
A tox.ini
A  => .gitignore +2 -0
@@ 1,2 @@
__pycache__
.venv

A  => 65.bf +30 -0
@@ 1,30 @@

[-] Clear result
>[-]+
[ Loop on left being nonzero
  >[-]++++
  [ 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
        <[-]> Clear right and move to bit
    ]
    <
  ]
  <<+> Incr result and move to left
]
< Move to result
.
\ No newline at end of file

A  => bfcomp.py +162 -0
@@ 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)

A  => bfcomp_test.py +38 -0
@@ 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])

A  => tox.ini +3 -0
@@ 1,3 @@
[flake8]
max-line-length = 100