Brainbalm: An easier-to-program interface to Brainfuck
------------------------------------------------------
Here are some →mac←ros for a simple interface to Brainfuck. The macros expand to
regular "pure" BF code, which uses only tokens: [ ] + - < > , .

The Balm machine extends BF with:

    * If-else-fi constructions, which may be nested.
    * A push-down stack (for non-negative values).
    * Directly addressable (random-access) memory.
    * Instructions ; and : for input/output of numeric digits.

This little function "∆" will help us to collect sample source lines into a new-
line-separated line-vector in buffer ⍙:

    ∆←{{}⍙,←⍵,⎕ucs 13}      ⍝ accumulate into ⍙ buffer.
    ⍙←''                    ⍝ null accumulation buffer.

First, we implement directly-addressable (random-access)  memory in BF, with the
home (current) cell used as an accumulator "A". In addition, internal, invisible
"registers" b and c will be used for some copy operations and will be assumed to
be zero between instructions.

     ┌─┬─┬─────────── Accumulator A and registers b and c.
    ┬─┬─┼A┬─┬─┬─┬─┬─┬─
    │c b│ │ │ │ │ │ │ ∘∘∘
    ┴─┴─┼0┴1┴2┴3┴4┴5┴─
         └─┴─┴─┴─┴─┴── "memory addresses" 0 1 2 3 4 5 ...

Macros ≡, ← and → implement directly-addressable or random-access BF memory:

    ∆'≡=(           '   ⍝ ≡ copy <n> to A.  eg: n≡
    ∆'  />          '   ⍝   go to <n>
    ∆'  [-/<+<+>/>] '   ⍝   move <n> to A and b.
    ∆'  /<          '   ⍝   go to A.
    ∆'  <           '   ⍝   go to b.
    ∆'  [->/>+/<<]  '   ⍝   move b to <n>
    ∆'  >           '   ⍝   go to A.
    ∆')             '

    ∆'→=(           '   ⍝ → move <n> to A.  eg: n→
    ∆'  />          '   ⍝   go to <n>
    ∆'  [-/<+/>]    '   ⍝   move <n> to A.
    ∆'  /<          '   ⍝   go to A.
    ∆')             '

    ∆'←=(           '   ⍝ ← move A to <n>   eg: n←
    ∆'  />[-]/<     '   ⍝   clear <n>
    ∆'  [-/>+/<]    '   ⍝   move A to <n>
    ∆')             '

    ram ⍙←⍙ ''          ⍝ directly-addressable memory macros ≡, ← and →.

Here are macros ↓ and ↑ for pushing (down) and popping (up)  stack items.  As we
rely on the presence of a value in the v field when searching leftwards for top-
of-stack, we can't push a value of 0.  No problem; we just add 1 before stacking
a value and subtract 1 after popping it.

             ┌────────────────────── top of stack
             │             ┌──────── stack base
       ─┬─┬─┬T┬─   ─┬─┬─┬─┬S┬─┬─┼A┬
   <-  0 0│t v│ ... │t v│t v│c b│ │
       ─┴─┴─┴─┴─   ─┴─┴─┴─┴─┴─┴─┼0┴

The  stack  is  to  the _left_ of the "home" cell A. If your BF system is fitted
with only a right-infinite  memory  tape,  you should use the ! macro to preface
your code with a ">>···>" sequence  long  enough  to  reserve  sufficient  stack
space.

    ∆'↓=(           '   ⍝ push:                     S←A,S ⋄ A←0
    ∆'  ←=[-/<+/>]  '   ⍝   ← move ⍺ cells left (local macro defn).
    ∆'  →=[-/>+/<]  '   ⍝   → move ⍺ cells right    ..  ..  ..
    ∆'  +           '   ⍝ increment value to be pushed
    ∆'  4←          '   ⍝ copy A to t in stack base (S.t)
    ∆'  <<<         '   ⍝ go (move BF's memory pointer) to S
    ∆'  [< 2← <]    '   ⍝ move S.t to (T+1).t, where (T+1) is left of T
    ∆'  < 1→ >      '   ⍝ move (T+1).t to (T+1).v
    ∆'  [>>]        '   ⍝ skip back to (S-1).v (aka b)
    ∆'  >           '   ⍝ go to A
    ∆')             '

    ∆'↑=(           '   ⍝ pop:                      A+←↑S ⋄ S←1↓A
    ∆'  →=[-/>+/<]  '   ⍝   → move ⍺ cells right
    ∆'  <<<         '   ⍝ go to S
    ∆'  [<<] >>     '   ⍝ go to T
    ∆'  1→          '   ⍝ move T.v to (T-1).t
    ∆'  >>          '   ⍝ go to T-1
    ∆'  [< 2→ > >>] '   ⍝ move value down stack to (S-1).t (aka c)
    ∆'  <           '   ⍝ go to c
    ∆'  2→          '   ⍝ move c to A
    ∆'  >>          '   ⍝ go to A
    ∆'  -           '   ⍝ decrement popped value.
    ∆')             '

    ∆'!=>>/>/>>>    '   ⍝ reserve space for ⍺-stack and regs b and c. Eg: 1000!

    stk ⍙←⍙ ''          ⍝ stack macros ↓ ↑ and !.

Macros ; and : code  the  standard  BF  sequences that convert between character
digits '0'-'9' and their numeric equivalents, by adding or subtracting '0':

    ∆';=(           '   ⍝ ; input digit   A←⎕
    ∆'  ,           '   ⍝   input char '0'-'9'.
    ∆'  ∆=/-        '   ⍝   minus.
    ∆'  <6∆[+>8∆<]> '   ⍝   subtract '0'=48.
    ∆')'

    ∆':=(           '   ⍝ : output digit  ⎕←A
    ∆'  ∆=/+        '   ⍝   plus.
    ∆'  <6∆[->8∆<]> '   ⍝   add '0'=48.
    ∆'  .           '   ⍝   output char '0'-'9'.
    ∆')             '

    io ⍙←⍙ ''           ⍝ numeric I/O macros ; and :.

and finally, here are {, ⋄ and }, which give us an if-else-fi control struct:

    ∆'  {=<+>[<->   '   ⍝ {  if A≠0
    ∆'  ⋄=<]<[->    '   ⍝ ⋄  else
    ∆'  }=<<]>>     '   ⍝ }  fi

    if ⍙←⍙ ''           ⍝ if-else-fi macros { ⋄ }.

Examples:

    dark ← ∩∘'[]<>+-,.'                         ⍝ without white space.

    opt ← {↑{({⍵⍱¯1⌽⍵}⍺⍷⍵)/⍵}⍣≡/'><' '<>'⍵}     ⍝ cancelling >< pairs removed.

    dark mac ram,io, 'm=1 n=2 ;m← ;n←' ⍝ BF: store input digits at [1] and [2].
,<------[+>--------<]>>[-]<[->+<],<------[+>--------<]>>>[-]<<[->>+<<]

    bf mac io,stk, '1! ;↓;↑:'   ⍝ sum of two input digits.
2
3
5

Here is a (very inefficient, compared with →bfack←) coding of Ackermann's funct-
ion. The function is usually written in →declarative← style as three cases:

    ack 0 n = n+1
    ack m 0 = ack (m-1) 1
    ack m n = ack (m-1) (ack m (n-1))

The  recursion  can  be  recast  using iteration if we provide an explicit stack
whose  top two items are the arguments for the next call. Compare the above with
the following pseudo-code:

    ack m n
    ·   push ¯1             ⍝ end of stacked-items marker.
    ·   repeat              ⍝ loop
    ·   ·   if m=0          ⍝ ack(0, n)
    ·   ·   ·   push n+1    ⍝   = n+1
    ·   ·   else if n=0     ⍝ ack(m, 0)
    ·   ·   ·   push m-1    ⍝   = ack(m-1, ─────────┐
    ·   ·   ·   push 1      ⍝         1)            │ "recursive"
    ·   ·   else            ⍝ ack(m, n)             │   calls.
    ·   ·   ·   push m-1    ⍝   = ack(m-1, ─────────┤
    ·   ·   ·   push m      ⍝         ack(m, ───────┘
    ·   ·   ·   push n-1    ⍝             n-1))
    ·   ·   fi              ⍝
    ·   ·   n ← pop         ⍝ ──┐ "recursive"
    ·   ·   m ← pop         ⍝ ──┘   return.
    ·   while m≠¯1          ⍝ while items on stack.
    ·   return n

It  will  simplify our task if we replace the "else if" construct in favour of a
simpler, though more deeply-nested, "if else fi".

    ack m n
    ·   push ¯1
    ·   repeat
    ·   ·   if m=0
    ·   ·   ·   push n+1
    ·   ·   else                ⍝ "else if" → "else" with an
    ·   ·   ·   if n=0          ⍝   indented "if"
    ·   ·   ·   ·   push m-1    ⍝
    ·   ·   ·   ·   push 1      ⍝
    ·   ·   ·   else            ⍝
    ·   ·   ·   ·   push m-1    ⍝
    ·   ·   ·   ·   push m      ⍝
    ·   ·   ·   ·   push n-1    ⍝
    ·   ·   ·   fi              ⍝ together with an additional "fi".
    ·   ·   fi
    ·   ·   n ← pop
    ·   ·   m ← pop
    ·   while m≠¯1
    ·   return n

Now we can factor the "push m-1" from both clauses of the "if n=0" case:

    ack m n
    ·   push ¯1
    ·   repeat
    ·   ·   if m=0
    ·   ·   ·   push n+1
    ·   ·   else
·   ·   ·   ·   push m-1        ⍝ factored-out push.
    ·   ·   ·   if n=0
    ·   ·   ·   ·   push 1
    ·   ·   ·   else
    ·   ·   ·   ·   push m
    ·   ·   ·   ·   push n-1
    ·   ·   ·   fi
    ·   ·   fi
    ·   ·   n ← pop
    ·   ·   m ← pop
    ·   while m≠¯1
    ·   return n

and reverse the sense of the tests from =0 to ≠0 by exchanging the corresponding
clauses.

    ack m n
    ·   push ¯1
    ·   repeat
    ·   ·   if m≠0              ⍝ test for non-0
·   ·   ·   ·   push m-1
    ·   ·   ·   if n≠0          ⍝ test for non-0
    ·   ·   ·   ·   push m
    ·   ·   ·   ·   push n-1
    ·   ·   ·   else
    ·   ·   ·   ·   push 1
    ·   ·   ·   fi
    ·   ·   else
    ·   ·   ·   push n+1
    ·   ·   fi
    ·   ·   n ← pop
    ·   ·   m ← pop
    ·   while m≠¯1
    ·   return n

Finally,  as  copying negative values would add significant complexity to the BF
coding, we add 1 to m and n throughout  and subtract one  from the final result.
This allows our end-of-stacked-items marker to be 0, rather than ¯1.

Here is the version of Ackermann's fuction that we will code in Brainfuck:

    ┌─Iterative Ackermann───────────┐
    │                               │
    │   ack m n                     │
    │   ·   m n +← 1                │
    │   ·   push 0                  │
    │   ·   repeat                  │
    │   ·   ·   if 0≠m-1            │
    │   ·   ·   ·   push m-1        │
    │   ·   ·   ·   if 0≠n-1        │
    │   ·   ·   ·   ·   push m      │
    │   ·   ·   ·   ·   push n-1    │
    │   ·   ·   ·   else            │
    │   ·   ·   ·   ·   push 1+1    │
    │   ·   ·   ·   fi              │
    │   ·   ·   else                │
    │   ·   ·   ·   push n+1        │
    │   ·   ·   fi                  │
    │   ·   ·   n ← pop             │
    │   ·   ·   m ← pop             │
    │   ·   while m≠0               │
    │   ·   return n-1              │
    │                               │
    └───────────────────────────────┘

    ⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝
    ⍝                                                       ⍝
    ⍝                           ⍝ Ackermann's function.     ⍝
    ∆'  m=1 n=2             '   ⍝   declare local vars.     ⍝
    ∆'  ;+m← ;+n←           '   ⍝   input and incr m and n  ⍝
    ∆'  ↓                   '   ⍝   push 0                  ⍝
    ∆'  m≡[                 '   ⍝   repeat                  ⍝
    ∆'  ·   -{              '   ⍝   ·   if 0≠m-1            ⍝
    ∆'  ·   ·   ↓           '   ⍝   ·   ·   push m-1        ⍝
    ∆'  ·   ·   n≡-{[-]     '   ⍝   ·   ·   if 0≠n-1        ⍝
    ∆'  ·   ·   ·   m→ ↓    '   ⍝   ·   ·   ·   push m      ⍝
    ∆'  ·   ·   ·   n→-↓    '   ⍝   ·   ·   ·   push n-1    ⍝
    ∆'  ·   ·   ⋄           '   ⍝   ·   ·   else            ⍝
    ∆'  ·   ·   ·    ++↓    '   ⍝   ·   ·   ·   push 1+1    ⍝
    ∆'  ·   ·   }           '   ⍝   ·   ·   fi              ⍝
    ∆'  ·   ⋄               '   ⍝   ·   else                ⍝
    ∆'  ·   ·   n→+↓        '   ⍝   ·   ·   push n+1        ⍝
    ∆'  ·   }               '   ⍝   ·   fi                  ⍝
    ∆'  ·   ↑n←             '   ⍝   ·   pop n               ⍝
    ∆'  ·   ↑m←             '   ⍝   ·   pop m               ⍝
    ∆'  m≡]                 '   ⍝   while stacked items     ⍝
    ∆'  n→-:                '   ⍝   output n-1              ⍝
        ack ⍙←⍙ ''                                          ⍝
    ⍝                                                       ⍝
    ⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝⍝

    bf opt dark mac if,io,ram,stk, '10!',ack    ⍝ ack 2 3 → 9
2
3
9
    ⍴opt dark mac if,io,ram,stk,ack             ⍝ Size of BF code for Ackermann.
601

⍝ Using this coding, ack(3,3) takes around half an hour on a 2GHz machine.
⍝ Compare this with around 15 seconds for the coding in →bfack←.

This function wraps and indents BF loops for easier reading:

    pretty←{⎕ML←1
        nl←⊃⌽⎕TC
        dents←{∊¨(0⌈⍵+1 ¯1)⍴¨⊂⊂4↑'·'}
        0{
            nice(next nasty)←⍵
            '∘'=next:∊nice
            more less←dents ⍺
            '['=next:(⍺+1)∇(nice,next,nl,more)nasty
            ']'=next:(⍺-1)∇(nice,nl,less,next)nasty
            ⍺ ∇(nice next)nasty
        }↑{⍺ ⍵}/⍵,'∘'
    }

    pretty opt dark mac if,io,ram,stk,ack   ⍝ Ackermann's function from Balm.
,<------[
·   +>--------<
]>+>[
·   -
]<[
·   ->+<
],<------[
·   +>--------<
]>+>>[
·   -
]<<[
·   ->>+<<
]+[
·   -<<<<+>>>>
]<<<[
·   <[
·   ·   -<<+>>
·   ]<
]<[
·   ->+<
]>[
·   >>
]>>[
·   -<+<+>>
]<<[
·   ->>+<<
]>[
·   -<+>[
·   ·   <->+[
·   ·   ·   -<<<<+>>>>
·   ·   ]<<<[
·   ·   ·   <[
·   ·   ·   ·   -<<+>>
·   ·   ·   ]<
·   ·   ]<[
·   ·   ·   ->+<
·   ·   ]>[
·   ·   ·   >>
·   ·   ]>>>[
·   ·   ·   -<<+<+>>>
·   ·   ]<<<[
·   ·   ·   ->>>+<<<
·   ·   ]>-<+>[
·   ·   ·   <->[
·   ·   ·   ·   -
·   ·   ·   ]>[
·   ·   ·   ·   -<+>
·   ·   ·   ]<+[
·   ·   ·   ·   -<<<<+>>>>
·   ·   ·   ]<<<[
·   ·   ·   ·   <[
·   ·   ·   ·   ·   -<<+>>
·   ·   ·   ·   ]<
·   ·   ·   ]<[
·   ·   ·   ·   ->+<
·   ·   ·   ]>[
·   ·   ·   ·   >>
·   ·   ·   ]>>>[
·   ·   ·   ·   -<<+>>
·   ·   ·   ]<<-+[
·   ·   ·   ·   -<<<<+>>>>
·   ·   ·   ]<<<[
·   ·   ·   ·   <[
·   ·   ·   ·   ·   -<<+>>
·   ·   ·   ·   ]<
·   ·   ·   ]<[
·   ·   ·   ·   ->+<
·   ·   ·   ]>[
·   ·   ·   ·   >>
·   ·   ·   ]
·   ·   ]<[
·   ·   ·   ->+++[
·   ·   ·   ·   -<<<<+>>>>
·   ·   ·   ]<<<[
·   ·   ·   ·   <[
·   ·   ·   ·   ·   -<<+>>
·   ·   ·   ·   ]<
·   ·   ·   ]<[
·   ·   ·   ·   ->+<
·   ·   ·   ]>[
·   ·   ·   ·   >>
·   ·   ·   ]<
·   ·   ]>
·   ]<[
·   ·   ->>>[
·   ·   ·   -<<+>>
·   ·   ]<<++[
·   ·   ·   -<<<<+>>>>
·   ·   ]<<<[
·   ·   ·   <[
·   ·   ·   ·   -<<+>>
·   ·   ·   ]<
·   ·   ]<[
·   ·   ·   ->+<
·   ·   ]>[
·   ·   ·   >>
·   ·   ]<
·   ]<[
·   ·   <<
·   ]>>[
·   ·   ->+<
·   ]>>[
·   ·   <[
·   ·   ·   ->>+<<
·   ·   ]>>>
·   ]<[
·   ·   ->>+<<
·   ]>>->>[
·   ·   -
·   ]<<[
·   ·   ->>+<<
·   ]<<<[
·   ·   <<
·   ]>>[
·   ·   ->+<
·   ]>>[
·   ·   <[
·   ·   ·   ->>+<<
·   ·   ]>>>
·   ]<[
·   ·   ->>+<<
·   ]>>->[
·   ·   -
·   ]<[
·   ·   ->+<
·   ]>[
·   ·   -<+<+>>
·   ]<<[
·   ·   ->>+<<
·   ]>
]>>[
·   -<<+>>
]<<-<++++++[
·   ->++++++++<
]>.

See also: bfack bf mac

Back to: contents

Back to: Workspaces