Skip to content

Latest commit

 

History

History
126 lines (90 loc) · 6.3 KB

File metadata and controls

126 lines (90 loc) · 6.3 KB

Navigation

Table of Contents

BF: the classical human-incomprehensible automaton

A BF to Python source-to-source compiler.

Powered by mcpyrate.

from unpythonic.dialects.bf import dialects, BF  # noqa: F401

# 'A' via a 5 × 13 multiplication loop
+++++++++++++[>+++++<-]>.

Features

  • Cell semantics: 8-bit wrapping cells.
    • The tape is a defaultdict[int, int] subclass (unpythonic.dialects.bf.Tape) whose __setitem__ masks assigned values to the range 0..255.
    • The pointer is unbounded in either direction (infinite Turing tape); untouched cells read as zero.
  • Folding: consecutive identical commands collapse (+++tape[ptr] += 3).
    • No cancellation of opposites — +- and >< emit both operations, what you wrote is what you get.
  • Loops: [ compiles to while tape[ptr]: plus an indent; ] dedents.
    • An empty loop body gets a pass.
  • I/O: . writes chr(tape[ptr]) to stdout; , reads one character from stdin. On EOF, , stores 0 in the current cell.
  • Comments: classical BF treats any non-command character as a no-op.
    • The dialect preserves the text — consecutive runs of non-command characters compile into Python # ... comments, positioned where they appeared in the source.
    • A leading # in the BF source is passed through cleanly, so both # real comment and bare real comment come out as # real comment in the compiled Python.
  • reset: a line whose stripped content is exactly reset compiles to tape.clear(); ptr = 0. This lets several BF programs share one file.

Reading the compiled Python

BF is a transpiler: it takes a BF program and emits human-readable Python that does the same thing. The compiled output is intentionally legible — +++ becomes tape[ptr] += 3, […] becomes while tape[ptr]: …, comments are preserved as Python comments — so reading the Python is a perfectly good way to understand a non-trivial BF program. The pedagogic value of the dialect lives in the output text, not in any stored knowledge of the input.

Two ways to actually see the compiled Python:

1. Programmatically, via bf.compile. Useful for offline inspection, ad-hoc experiments, and printing the compiled form into a notebook or a paper:

from unpythonic.dialects import bf

src = "+++++++++++++[>+++++<-]>."  # 'A' via a 5×13 multiplication loop
print(bf.compile(src))

For the program above, this prints:

from sys import stdin, stdout
from unpythonic.dialects.bf import Tape
tape = Tape()
ptr = 0

tape[ptr] += 13
while tape[ptr]:
    ptr += 1
    tape[ptr] += 5
    ptr -= 1
    tape[ptr] -= 1
ptr += 1
stdout.write(chr(tape[ptr])); stdout.flush()

The qualified bf.compile form is recommended over from … import compile to avoid shadowing builtins.compile in the importer's namespace.

2. Live, while running the dialect file, via mcpyrate.debug.StepExpansion. When StepExpansion is the first dialect in the import chain, the dialect expander prints the source after each transformer pass — so for a BF file it shows the BF body before transformation and the generated Python afterward, then runs the result:

from mcpyrate.debug import dialects, StepExpansion
from unpythonic.dialects.bf import dialects, BF

+++++++++++++[>+++++<-]>.

Run via macropython (or by import-ing the file) and the BF→Python translation is printed to stderr alongside the program's normal execution. Useful when the BF source already lives inside a .py file and you'd rather not retype it as a string for bf.compile.

StepExpansion is documented in mcpyrate's troubleshooting guide. It works for any dialect, not just BF.

What BF is

BF is a dialect of Python implemented as a whole-module source-to-source transform. The dialect definition lives in unpythonic.dialects.bf. Usage examples can be found in the unit tests.

It's also a minimal example of how to make a source-transforming dialect, the modern equivalent of what old Lisp folks used to call a reader macro. All other dialects in this collection — Lispython, Listhell, Pytkell — are AST-transforming, built on top of unpythonic's macro layer. BF shares none of that machinery: the body of a BF file is not parseable as Python at all, so the compiler runs at the text level, before mcpyrate's AST-level dialect stage.

Comboability

Source-transforming dialects consume the whole module body, so combining BF with another source-transforming dialect on the same file doesn't really make sense. Composition with AST-transforming dialects is supported: from X import dialects, BF, SomeOptimizer (or on separate from lines) places SomeOptimizer after BF in the transform chain, running its AST pass on the output of the BF compiler.

The mechanism is the mcpyrate.dialects.split_at_dialectimport helper (new in mcpyrate 4.1.0): BF's transform_source uses it to peel off its own dialect-import line while preserving any others for the next round of dialect processing.

CAUTION

Not intended for serious use.

Etymology?

See Wikipedia.