20
\$\begingroup\$

You are to take a string representing a piece of brainfuck code and Explanations, containing only printable ASCII characters and newlines ( to ~, ASCII 10 and 32 to 126) as input and output an explanation of that code, formatted to be compliant with Stack Exchange markdown.

That is, the explanation must satisfy:

  • Each line has an additional leading space, or multiple if the line above has multiple characters of code, such that it remains aligned vertically
  • All commands are on their own line, while any consecutive no-ops (anything that isn't one of <>+-.,[]) are grouped together on one line
  • The "explanation" for each command are all aligned to a single column, one space after the code itself
  • The entire thing is in a "code block".
    • Either the entire explanation has a preceding and trailing ``` line, or
    • The entire explanation has a preceding <pre><code> line and a trailing </code></pre>
    • Each line of the explanation has 4 leading spaces, in addition to any leading spaces it might already have.
    • You don't have to escape parts of the output that might break the markdown.
  • The first line of code block is the BF input code.

For example, if the code is +[]Loop, the explanation (without "explanations") would be formatted as one of

    +[]Loop
    +      
     [     
      ]    
       Loop

<pre><code>+[]Loop
+      
 [     
  ]    
   Loop
</code></pre>

```
+[]Loop
+      
 [     
  ]    
   Loop
```

Explanations

The explanation for each character will be provided as input:

Move the pointer to the right
Move the pointer to the left
Increment the memory cell at the pointer
Decrement the memory cell at the pointer
Output the character signified by the cell at the pointer
Input a character and store it in the cell at the pointer
Jump past the matching ] if the cell at the pointer is 0
Jump back to the matching [ if the cell at the pointer is nonzero
No Operation

The input always contain 9 explanation strings.
Answers can choose how to associate each brainfuck command with each explanation.
You may use array.
We can now add these explanations to our sample code to get

    +[]Loop
    +       Increment the memory cell at the pointer
     [      Jump past the matching ] if the cell at the pointer is 0
      ]     Jump back to the matching [ if the cell at the pointer is nonzero
       Loop No Operation

(using 4 leading spaces instead of backtick fencing or HTML tags).

This is , so the shortest code in bytes wins.

Testcases

Input:

[]<>{}()++--..,,
Move the pointer to the right
Move the pointer to the left
Increment the memory cell at the pointer
Decrement the memory cell at the pointer
Output the character signified by the cell at the pointer
Input a character and store it in the cell at the pointer
Jump past the matching ] if the cell at the pointer is 0
Jump back to the matching [ if the cell at the pointer is nonzero
No Operation

Output:

    []<>{}()++--..,,
    [                Jump past the matching ] if the cell at the pointer is 0
     ]               Jump back to the matching [ if the cell at the pointer is nonzero
      <              Move the pointer to the left
       >             Move the pointer to the right
        {}()         No Operation
            +        Increment the memory cell at the pointer
             +       Increment the memory cell at the pointer
              -      Decrement the memory cell at the pointer
               -     Decrement the memory cell at the pointer
                .    Output the character signified by the cell at the pointer
                 .   Output the character signified by the cell at the pointer
                  ,  Input a character and store it in the cell at the pointer
                   , Input a character and store it in the cell at the pointer

Input:

Brainfuck Power.
Move the pointer to the right
Move the pointer to the left
Increment the memory cell at the pointer
Decrement the memory cell at the pointer
Output the character signified by the cell at the pointer
Input a character and store it in the cell at the pointer
Jump past the matching ] if the cell at the pointer is 0
Jump back to the matching [ if the cell at the pointer is nonzero
No Operation

Output:

```
Brainfuck Power.
Brainfuck Power  No Operation
               . Output the character signified by the cell at the pointer
```

Input:

>++++++[{{<,.>-}}]
Move the pointer to the right
Move the pointer to the left
Increment the memory cell at the pointer
Decrement the memory cell at the pointer
Output the character signified by the cell at the pointer
Input a character and store it in the cell at the pointer
Jump past the matching ] if the cell at the pointer is 0
Jump back to the matching [ if the cell at the pointer is nonzero
No Operation

Output:

<pre><code>>++++++[{{<,.>-}}]
>                  Move the pointer to the right        
 +                 Increment the memory cell at the pointer           
  +                Increment the memory cell at the pointer
   +               Increment the memory cell at the pointer
    +              Increment the memory cell at the pointer
     +             Increment the memory cell at the pointer
      +            Increment the memory cell at the pointer
       [           Jump past the matching ] if the cell at the pointer is 0
        {{         No Operation
          <        Move the pointer to the left
           ,       Input a character and store it in the cell at the pointer
            .      Output the character signified by the cell at the pointer
             >     Move the pointer to the right
              -    Decrement the memory cell at the pointer
               }}  No Operation
                 ] Jump back to the matching [ if the cell at the pointer is nonzero
</code></pre>

Input:

><+-.,NOP[]
Move
Move
Increment
Decrement
STD
STD
While
While
NOP

Output:

    ><+-.,NOP[]
    >           Move
     <          Move
      +         Increment
       -        Decrement
        .       STD
         ,      STD
          NOP   NOP
             [  While
              ] While

Input:

-\
Blah
Blah
Blah
Decrement
Blah
Blah
Blah
Blah
Nothing happens here

Output:

-\
-  Decrement
 \ Nothing happens here
\$\endgroup\$
8
  • 3
    \$\begingroup\$ if nobody makes an answer for this in brainfuck im putting a bounty on that \$\endgroup\$ Nov 18, 2021 at 14:17
  • \$\begingroup\$ @thejonymyster can you add to list of bounties with no deadline \$\endgroup\$
    – Fmbalbuena
    Nov 18, 2021 at 14:31
  • 1
    \$\begingroup\$ @Fmbalbuena The Bounty... Is Begins \$\endgroup\$ Nov 18, 2021 at 14:49
  • 1
    \$\begingroup\$ What does "Answers can choose how to associate each brainfuck command with each explanation." mean? Can we output the first inputted description (e.g. "Move the pointer to the right") for every command? (I assume no) \$\endgroup\$
    – tjjfvi
    Nov 24, 2021 at 0:47
  • 1
    \$\begingroup\$ @thejonymyster Here you go! \$\endgroup\$
    – tjjfvi
    Nov 24, 2021 at 16:16

15 Answers 15

19
+250
\$\begingroup\$

brainfuck, 811 775 709 701 bytes

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

Try it online!

This could probably be golfed more, as this is, like, the second brainfuck program I've written.

Explanation

I should really write a program to do this for me...

Fullscreen Explanation

#####
# Execution will be divided into two phases: input and output.
# The input phase will input all characters and lay them out into memory with metadata.
# The output phase will then loop over the memory and print various segments of it, forming the explanation.

###############
# Input Phase #
###############

#####
# First, let's take a look at the memory layout.
# We'll divide the memory into 6-byte chunks.
# The chunks will be laid out in this format:
#   - first header chunk, to denote the start
#   - second header chunk, to denote the start
#   - first source chunk
#   - second source chunk
#   - ...
#   - last source chunk
#   - primary separator chunk (in place of newline)
#   - first chunk of first explanation
#   - second chunk of first explanation
#   - ...
#   - last chunk of first explanation
#   - separator chunk (in place of newline)
#   - first chunk of second explanation
#   - ......
#   - last chunk of last explanation
#   - eof chunk
# The first header chunk will have the following bytes:
#   0 10 96 0 0 0
# (10 is newline, and 96 is backtick)
# The second header chunk will consist entirely of zeroes.

                     #####
                     # Initialize the header chunks and output the initial code fence:
>>                     # Move to cell #2 (0-indexed).
-[-[--->]<<-]>--       # Set cell #2 to backtick (96) (pulled from https://esolangs.org/wiki/Brainfuck_constants).
...                    # Print it thrice (fine, there is a tiny bit of output in this phase).
<++++++++++.           # Set cell #1 to newline (10) and print it.
>>>>>>>>>>+          # Move to cell #5 of the second header chunk and temporarily set it to 1.

                     #####
                     # Next, read the input into chunks and initialize them.
                     # Every non-header non-eof chunk will be initialized to the following format:
                     #   #0: [char]  (0 if separator)
                     #   #1: [kind]  (which operator the char is)
                     #   #2: [mark]  (currently 1; this will occasionally be temporarily set to 0 as a "bookmark")
                     #   #3: 32      (the code for space, used for output)
                     #   #4: [noop]  (1 if no-op source chunk or non-primary separator, 0 otherwise)
                     #   #5: [done]  (currently 0, undone)
                     # During the initialization, some cells are used for temporary values.
[                    # While the current cell (#5 of the preceding chunk) is non-zero (and thus known to be 1), loop over the input:
  -                    # Subtract 1 from the cell, [done], returning it to 0 (undone)
  >                    # Move to the active chunk.
                       # Cell #5 will be used for positioning the pointer after a branch ('alignment'):
  >>>>>+               # Set cell #5 to 1
                       #####
                       # Next, we'll read a character.
  <<,                  # Move to cell #3 and input a character into it (temp).
  [                    # If this character is non-zero (not eof):
    [-<+<+>>]            # Duplicate this character into cells #1 and #2 [temp], erasing this cell, #3.
    <----------          # Move to cell #2 and subtract 10 (code for newline) from it.
                         # The chunk is now:
                         #   #0: 0
                         #   #1: char (temp)
                         # > #2: char - 10 (temp)
                         #   #3: 0
                         #   #4: 0
                         #   #5: 1 (alignment)
                         #####
                         # Next, if char is newline (10), we'll set cell #1 to 0.
    [[-]->]              # If cell #2 (char - 10) is non-zero (the char is not a newline), set this cell to 255 and move to cell #3.
    -[+>-]               # Move to the first cell to the right set to 1 (cell #5), and set it to 0.
                         # (The above is done without any net change to other cells.) 
    <<<+                 # Move to cell #2 and add one to it; this cell is now 1 if char is a newline, or 0 otherwise.
    [-<[-]>]             # If cell #2 is 1, set it back to 0, move to cell #1, clear it, and move back to cell #2.
                         # From now on, char will be 0 if the character was a newline.
                         # The chunk is now:
                         #   #0: 0
                         #   #1: char (temp)
                         # > #2: 0
                         #   #3: 0
                         #   #4: 0
                         #   #5: 0
                         #####
                         # Next we'll inialize cell #0 [char] and put a copy of char into cell #2.
    <                    # Move to cell #1.
    [-<+>>+<]            # Duplicate this value to cells #0 and #2, clearing this cell.
                         # The chunk is now:
                         #   #0: [char]
                         # > #1: 0
                         #   #2: char (temp)
                         #   #3: 0
                         #   #4: 0
                         #   #5: 0
                         #####
                         # Next, we'll initialize cell #1 [kind] to a number denoting the 'kind' of the char is.
                         # The kind is an index from 0 to 8 matching the order "N+-<>[],." (where N stands for any no-op).
                         # The kind could be determined by directly checking if the character matches an operation (char == op).
                         # In BF, however, it's easier to check if something is not equal to X than if it is equal to X.
                         # We'll start cell #1 [kind] at the sum of the kind, 36 (1+2+3+4+5+6+7+8).
                         # For each operation, we'll check if the character does not match that operation, (char != op).
                         # To do so, we will use cell #2 for checking whether (char - op) is non-zero.
                         # If the character does not match that operation, we will subtract its kind from the sum.
                         # After doing so for all of the operations, the result will be the kind of the operation that the character matches.
                         # We'll do this not in kind order, but in op order: "+" (43), then "," (44), then "-" (45), etc.
                         #####
>>++++++[-<<++++++>>]    # Set cell #1 to 36 and move to cell #3.
>+                       # Set cell #4 to 1 to be used for alignment.
                         # The chunk is now:
                         #   #0: [char]
                         #   #1: 36 (sum of all kinds)
                         #   #2: char (char - 0)
                         #   #3: 0
                         # > #4: 1 (alignment)
                         #   #5: 0
                         #####
                         # The next set of lines are based on using the following pattern for each of the operations:
                           # Subtract from cell #2 the difference between the char codes for this operation and the previous.
                           # If cell #2 is not zero (if char != op):
                             # Remove this kind from the sum by subtracting from cell #1 (kind_sum) the kind of this operation.
                           # Return to cell #2 (using cell #4 for alignment).
                           # The resulting chunk will be:
                           #   #0: [char]
                           #   #1: kind_sum
                           # > #2: char (char - op)
                           #   #3: 0
                           #   #4: 1 (alignment)
                           #   #5: 0
                         # For '+', check 43:
    <++++++[-<------->]<-  # Subtract 43 (6 * 7 + 1) from cell #2, the difference between '+' and 0.
                           # The value in cell #2 is now char - 43.
    [                      # If the cell #2 is non-zero (char != 43; the character is not '+'):
      <->>                   # Subtract from cell #1 the kind of '+', 1.
    ]                      # End if.
    >[<]<                  # Return to cell #2 (using cell #4 for alignment).
                         # (For the remaining operations, the pattern will be shown in a condensed form.)
                         # For ',', check 44: 
    -                      # Subtract 1 (the difference between ',' and '+').
    [<------->>]>[<]<      # If not zero, remove 7 from kind_sum. Return to cell #2.
                         # For '-', check 45:
    -                      # Subtract 1.
    [<-->>]>[<]<           # If not zero, remove 2 from kind_sum. Return to cell #2.
                         # For '.', check 46: 
    -                      # Subtract 1.
    [<-------->>]>[<]<     # If not zero, remove 8 from kind_sum. Return to cell #2.
                         # For '<', check 60:
    --------------         # Subtract 14.
    [<--->>]>[<]<          # If not zero, remove 3 from kind_sum. Return to cell #2.
                         # For '>', check 62:
    --                     # Subtract 2.
    [<---->>]>[<]<         # If not zero, remove 4 from kind_sum. Return to cell #2.
                         # For '[', check 91
    >+++++[-<------>]<+    # Subtract 29 (5 * 6 - 1).
    [<----->>]>[<]<        # If not zero, remove 5 from kind_sum. Return to cell #2.
                         # For ']', check 93
    --                     # Subtract 2.
    [<------>>]>[<]<       # If not zero, remove 6 from kind_sum. Return to cell #2.
                         #
    [-]                  # Set cell #2 to zero.
                         # The chunk is now:
                         #   #0: [char]
                         #   #1: [kind]
                         # > #2: 0
                         #   #3: 0
                         #   #4: 1 (temp)
                         #   #5: 0
                         #####
                         # Next, cell #4 [noop] will be initialized to 0 if char is not a no-op.
    <<+                  # Move to cell #0, adding 1 (temporarily, for alignment)
    >                    # Move to cell #1.
    [>>>-]               # If cell #1 [kind] is non-zero (the char is not a no-op),
                         #  move to cell #4 [noop] and subtract 1 to set it to 0.
    <[>>>]               # Move to cell #3, using cell #0 for realignment.
    <<<-                 # Move to cell #0 and subtract 1 from it, returning it to char.
                         # The chunk is now:
                         # > #0: [char]
                         #   #1: [kind]
                         #   #2: 0
                         #   #3: 0
                         #   #4: [noop] (1 if no-op, 0 otherwise)
                         #   #5: 0
                         #####
                         # Next, cell #2 [mark] will be initialized to 1 (unmarked) and cell #3 [space] will be initialized to 32 (code for space).
    >>++++[->++++++++<]  # Move to cell #2 and set cell #3 to 32 (4 * 8)
    +                    # Set #2 [mark] to 1 (unmarked)
    >>>+                 # Move to cell #5 and set it to 1 (temporarily, so the input loop continues; this will be unset in the next iteration).
    >                    # Move to cell #0 of the next chunk.
  ]                    # End "if non-eof".
  <                  # If at cell #0 of the next chunk (from the preceding branch for processing a source chunk), move to cell #5 of the current chunk;
                     #   otherwise (at cell #3 after the source chunks), move to cell #2.
]                    # End input loop.
                     # Every non-header non-eof chunk is now:
                     #   #0: [char]  (0 if separator)
                     #   #1: [kind]  (which operator the char is)
                     #   #2: [mark]  (currently 1; this will occasionally be temporarily set to 0 as a "bookmark")
                     #   #3: 32      (the code for space, used for output)
                     #   #4: [noop]  (1 if no-op source chunk or non-primary separator, 0 otherwise)
                     #   #5: [done]  (currently 0, undone)
                     # Since the loop ended, it reached eof, and the head is at cell #2 of the eof chunk.

################
# Output Phase #
################

                     #####
                     # First, we will return to the header chunks.
+                    # Set cell #2 to 1 (used later to save a byte).
[<<<<<<]             # Move left one chunk at a time until cell #2 is zero, reaching the second header chunk.
>>>>                 # Move to cell #0 of the first source chunk.
[.>>>>>>]            # While cell #0 is non-zero, print it and move to the next chunk 
                     #  (this writes the first line of the explanation, the code verbatim).
                     # Now, the head is at cell #0 of the primary separator chunk.
>>>>-<<<<            # Set its cell #4 to 0 (used later to break a loop).
<<<<<<[<<<<<<]       # Move to the next 0-char chunk to the left (this is the second header chunk).
<<<<<.               # Move to cell #1 of the first header chunk (newline) and output it (ending the first line).

                     #####
                     # As we go through each line of the explanation, we'll follow this psuedo-code:
                     #   - Output a space for every source character marked as 'done'
                     #   - Output the characters for the active operations (multiple if it's a chain of no-ops, only one otherwise)
                     #   - Mark all active operations 'done'
                     #   - Output a space for every source character after the active ones
                     #   - Output a space followed by the associated explanation
                     # These steps will be expanded on later.

>>>                  # Move to cell #4 of the first header chunk.
+                    # Set it to one to start the loop.
[                    # Begin a loop to print subsequent lines:
                       #####
                       # First, we will find the first not done chunk.
  -                    # Set this cell, #4 of the first header chunk, back to 0.
  >>>>>>>>>>>>>        # Move to cell #5 [done] of the first source chunk.
  [>>>>>>]             # Go to the first not done cell to the right (it might be the current chunk).
  <<<<<                # Go to cell #0 [char].
  [                    # If it's non-zero (if it's a source chunk):
                         #####
                         # Next, we will output the leading spaces and the first active character.
    <[<<.<<<<]           # Go to cell #5 of the first not done chunk to the left
                         #  (this is the second header chunk), outputting spaces along the way.
    >>>>>>               # Go to cell #5 [done] of the next chunk.
    [>>>>>>]             # Move to cell #5 of the first non-done cell to the right.
    <<<<<.               # Move to cell #0 [char] of this chunk (the first active source chunk) and output it.
    >>>>                 # Move to cell #4 [noop] of this chunk.
  
                         #####
                         # Next, the following branch will process a sequence of no-op chunks. 
                         # This is handled differently from non-no-op chunks because sequential no-op characters are printed in one line.
    [                    # If it's non-zero (if this character is a no-op):
      >+                   # Set cell #5 [done].
                           #####
                           # First, we will output each subsequent active character and mark their chunks as done.
      >>>>>                # Move to cell #4 [noop] of the next chunk.
      [                    # While the current chunk is a no-op:
        <<<<.                # Move to cell #0 [char] and output it.
        >>>>>+               # Move to cell #5 [done] and set it to 1.
        >>>>>                # Move to cell #4 [noop] of the next chunk.
                             # (Note that the primary separator has cell #4 [noop] set to 0 in order to break this loop)
      ]                    # End loop. Now the head is at the cell #4 [noop] of the first non-no-op chunk.
                           #####
                           # Next, we will output the trailing spaces and the no-op explanation.
      <<<<[>>>.>>>]        # Move to the primary separator chunk, outputting a space for each skipped chunk.
      >>>.>>>              # Move to cell #0 of the first chunk of the first explanation, outputting a space.
      [.>>>>>>]            # Move to the next non-explanation chunk, outputting each character.
      >>                   # Move to cell #2 [mark].
      [<<<<<<]             # Move to cell #2 of the second header chunk.
      <                    # Move to cell #1.
    ]                    # End if.
    <<<                  # If at cell #1 of the second header chunk (from the preceding branch for processing a no-op), move to cell #4 of the first header chunk;
                         #  otherwise, (still at the cell $4 [noop] of the current source chunk), move to cell #1 [kind].

                         #####
                         # Next, the following branch will process a non-no-op chunk.
                         # This is handled separately from no-op chunks because operators are printed on separate lines.
    [                    # If cell #1 [kind] is non-zero (if in a non-no-op source chunk):
                           #####
                           # First, we will output the trailing spaces and find the primary separator chunk.
      >-                   # Move to cell #2 [mark] and set it to 0 (this marks the cell to return to later).
      >>>+                 # Move to cell #5 [done] and set it to 1.
      >[>>>.>>>]           # Move to the primary separator chunk, outputting a space for each skipped chunk.
      >>-                  # Mark this chunk (the primary separator chunk).
      <<<<<.<              # Move to cell #2 [mark] of the previous chunk, outputting a space.
      [<<<<<<]             # Move to the first marked (mark == 0) cell to the left (the active source chunk).
      <                    # Move to cell #1 [kind].
                           #####
                           # Next, we'll locate the correct explanation.
                           # We're at cell #2 [kind], which acts as an index to the explanation.
                           # The primary separator (which precedes the 0th (no-op) explanation) is marked.
      [-                   # Continually decrement the kind:
                             # Each iteration will unmark the currently marked separator and mark the next separator.
        >>>>>>>[>>>>>>]      # Move to the first marked cell to the right.
        +                    # Unmark this chunk.
        >>>>                 # Move to cell #0 of the next chunk.
        [>>>>>>]             # Move through the explanation chunks to the next separator chunk.
        >>-                  # Mark this separator chunk.
        <<<<<<[<<<<<<]<      # Return cell #1 [kind] of the active source chunk (as it's marked).
      ]                    # End loop.
                           #####
                           # Next, we will output the explanation we have located.
      >+                   # Unmark the current source chunk.
      [>>>>>>]             # Move to the marked separator chunk.
      +                    # Unmark this chunk.
      >>>>                 # Move to cell #0 [char] of the next chunk.
      [.>>>>>>]            # While cell #0 [char] is non-zero, output it and move to the next chunk.
      >>[<<<<<<]           # Return to the second header chunk.
      <<<<                 # Move to cell #4 of the first header chunk.
    ]                    # End if.

                         # From either branch, we are now at cell #4 of the first header chunk.
    +                    # Set cell #4 to 1 (to continue the loop).
    <<<.                 # Move to cell #1 (newline) and output it.
    >>                   # Move to cell #3 (which is 0).
  ]                    # End if.
  >                    # If at the first header chunk (from the preceding branch for processing a source chunk), move to cell #4 (which is 1, continuing the loop);
                       #  otherwise (still at the primary separator), move to cell #1 (which is 0, ending the loop).
]                    # End loop.

#####
# Lastly, we will print the final code fence.
>[<<<<<<]            # Move to cell #2 of the second header chunk.
<<<<<<...            # Move to the first header chunk and output the backtick thrice.

#####
# Finally, we can enjoy the fruits of our efforts: https://pastebin.com/raw/C9yYHx3F.
\$\endgroup\$
5
  • 1
    \$\begingroup\$ @Fmbalbuena Seems to work to me? \$\endgroup\$
    – tjjfvi
    Nov 24, 2021 at 16:22
  • 1
    \$\begingroup\$ It was designed for an interpreter with 1-byte wrapping cells and unbounded array \$\endgroup\$
    – tjjfvi
    Nov 24, 2021 at 16:24
  • \$\begingroup\$ It doesn't need unbounded integers AFAIK \$\endgroup\$
    – tjjfvi
    Nov 24, 2021 at 16:24
  • \$\begingroup\$ Let us continue this discussion in chat. \$\endgroup\$
    – tjjfvi
    Nov 24, 2021 at 16:25
  • 1
    \$\begingroup\$ This explains itself lol. Although the explanations won't fit in a SE post \$\endgroup\$
    – emanresu A
    Nov 26, 2021 at 8:18
7
\$\begingroup\$

Vyxal oj, 69 bytes

:4$꘍,`([%^])`\\`><+-,.[]`f:→+∑%ṡ';:vL¦0pṪZƛ÷$꘍nh←=Tuw∨□ḣ∇‟İ„L›„↲p4$꘍∑

Try it Online!

Explained

`([%^])`

Push the string "([%^])" to the stack. This will serve as the basis for a regular expression that groups on non-BF commands

\\

Push a backslash to the stack. This will serve to escape all the characters in the BF character set ([ and ] need to be escaped, because they interfere with the range set)

`><+-,.[]`f

Push the string "><+-,.[]" to the stack, and turn it into a list of characters. It's important it's turned into a list of characters because the backslash will be prepended to each character using vectorised addition.

:→

But first, put the character set into the ghost variable for later storage - there's no BF character set element in vyxal, so to save bytes, it needs to be stored.

+∑

Prepend the backslash to each character just like I said I would, and convert it back to a single string.

%

And place that into the regex pattern string from earlier using string formatting

ṡ';

Split the input program on that regex, and keep only non-empty strings. This splits the program into single bf chars and groups of NOPs.

:vL¦0pṪZ

Get the length of each group, get the cumulative sum of that, prepend a 0 and zip with each group. This has the effect of calculating how many spaces to prepend to each group.

ƛ

To each item X = [spaces, character_group] in that, do the following

÷ð*p

Prepend spaces spaces to character_group

nh←=Tuw∨

Get the index of character_group in the BF character set stored in the ghost variable. This is basically a hacky version of because doesn't work on the version the site uses (it's fixed in the 2.6 preleases, but I'm sticking with 2.4.1)

□ḣ

Wrap all inputs (program and explanation strings) into a single list and push the program separately from the explanation strings.

∇‟İ

Do some weird stack manipulation to retrieve the corresponding explanation string using the index generated by nh←=Tuw∨.

„L›„↲$+

Left justify the character group with the prepended spaces with extra spaces to match the length of the input program + 1. And add that to the explanation using some extra weird stack manipulation.

4ð*p∑

Indent that 4 spaces, and convert into a single string.

;⁋

Join the entire list on newlines. The j flag would usually take care of this, but 69 is a funny number.

\$\endgroup\$
2
7
\$\begingroup\$

05AB1E, 53 52 50 bytes

.γ"><+-.,[]"©såN>*}DU¹g>j|VεXN>£JvÁ}Y®XNèkè«}¹š4ú»

-2 bytes thanks to @ovs.

Outputs the lines with four leading spaces.

Try it online.

Explanation:

.γ                 # Group the first (implicit) input-string into parts:
  "><+-.,[]"       #  Push the string "><+-.,[]"
            ©      #  Store it in variable `®` (without popping)
             s     #  Swap to get the current character
              å    #  Check if it's in the string
               N>* #  Multiply it by the 1-based index
                   #  (this ensures all characters in that string become unique
                   #  values, but all unknown characters map to the same value 0)
}DU                # After the group-by, store a copy in variable `X`
   ¹g              # Push the length of the first input-string
     >             # Increase it by 1
      j            # Make all strings of that length by padding leading spaces
|                  # Push a list of all remaining input-lines
 V                 # Pop and store it in variable `Y`
      ε            # Map over the parts:
       X           #  Push the parts from variable `X`
        N>£        #  Leave the first map-index + 1 amount of parts
           J       #  Join them together
            v }    #  Pop and loop its length amount of times:
             Á     #   Rotate the string once towards the right
         XNè       #  Get the map-index'th part from variable `X`
        ®   k      #  Get the index of that part from variable `®`
       Y     è     #  Use that to index into the list of lines `Y`
              «    #  Append the two strings together
      }¹š          # After the map: prepend the first input-string
         4ú        # Pad each string with 4 leading spaces
           »       # Join the list by newlines
                   # (after which the result is output implicitly)
\$\endgroup\$
1
  • 1
    \$\begingroup\$ I think the grouping part can be shortened to .γ"><+-.,[]"©såN>*}. \$\endgroup\$
    – ovs
    Nov 19, 2021 at 13:43
7
\$\begingroup\$

Charcoal, 54 48 47 bytes

× ⁴PSFθ«⊞υ⌕><+-.,[]ι¿⁺²Σ✂υ±²↓P⊟υι»↑⸿↓≔E⁹SηEυ§ηι

Try it online! Link is to verbose version of code. Explanation:

× ⁴

Indent by 4 spaces.

PS

Print the code without moving the cursor.

Fθ«

Loop over the characters in the code.

⊞υ⌕><+-.,[]ι

Push the index of the appropriate explanation to the predefined empty list. NOPs will have an index of -1, which will cyclically index to the last explanation, but is also useful for coalescing consecutive NOPs below.

¿⁺²Σ✂υ±²

If there are not two consecutive NOPs, then...

... move down a line, else...

P⊟υ

... discard one of the NOPs.

ι

Output the current character.

»↑⸿

Move to the start of the next column.

Move to the first row of explanations.

≔E⁹Sη

Read in the explanations.

Eυ§ηι

Print each appropriate explanation.

Today I discovered a bug in Charcoal's Split command which unintentionally results in partial regex support: Split(<string>, <list>) is supposed to split the string at any occurrence of any word in <list>, but due to the bug what actually happens is that Split(<pattern>, [<string>]) escapes the string and splits it on the pattern. Unfortunately the original approach has since been golfed down from its original 54 bytes which this approach ties with, but that's still good going considering the duplication of the command characters:

× ⁴≔Φ⪪“ xMT¦T~≧_x”⟦S⟧ιθP⪫θωFθ⁺¶ι↑⸿↓≔E⁹SηEθ§η⌕><+-.,[]ι

Try it online! Link is to verbose version of code. Note: Backslashes are not supported because this is relying on buggy Charcoal behaviour. Explanation:

× ⁴

Indent by 4 spaces.

≔Φ⪪“ xMT¦T~≧_x”⟦S⟧ιθ

Extract command characters or runs of non-command non-backslash characters from the input.

P⪫θω

Print the code without moving the cursor.

Fθ⁺¶ι

Output each command on its own line, but increasing the indent as it goes.

↑⸿

Move to the start of the next column.

Move to the first row of explanations.

≔E⁹Sη

Read in the explanations.

Eθ§η⌕><+-.,[]ι

Find and output the appropriate explanation for each command, or the last explanation for non-commands.

\$\endgroup\$
5
  • \$\begingroup\$ Protip: use backticks. \$\endgroup\$
    – Fmbalbuena
    Nov 23, 2021 at 10:11
  • \$\begingroup\$ @Fmbalbuena This is code-golf, and in Charcoal indenting the whole output by 4 spaces takes all of 3 bytes, while using backticks would probably take 8 bytes. \$\endgroup\$
    – Neil
    Nov 23, 2021 at 10:48
  • \$\begingroup\$ Wait I think backticks is shorter than ident by 4 spaces. \$\endgroup\$
    – Fmbalbuena
    Nov 23, 2021 at 10:55
  • \$\begingroup\$ @Fmbalbuena That's a clever trick given that removing the indent would only save 3 bytes... \$\endgroup\$
    – Neil
    Nov 23, 2021 at 12:15
  • \$\begingroup\$ This makes me to add a test case \$\endgroup\$
    – Fmbalbuena
    Jan 21, 2022 at 21:46
6
\$\begingroup\$

JavaScript (ES2022), 141 bytes

s=>a=>"    "+s+s.replace(/(?:[^+-.<>[\]]+|.)(?=(.*))/g,(c,e)=>`
`+(c+e.replace(/.|$/g,' ')).padStart(s.length+5)+a.at('><+-.,[]'.indexOf(c)))

Try it online!

f: (source_code: string) => (explain_array: string[]) => string
\$\endgroup\$
5
\$\begingroup\$

Pip, 65 60 bytes

OsX4FbPp:#_FIa^C X^Y"><+-.,[]"P[sX4pRXXsRA Uvbs(gU(y@?b)|9)]

The BF program and the nine explanation strings are taken as command-line arguments. Alternately, you can use the -r flag to take them from stdin. Replit! Or, here's a 61-byte equivalent in Pip Classic: Try it online!

Former explanation

New explanation pending... This is the explanation for the previous 65-byte solution.

Oi:sX4Pp:#_FIa^C X^Y"><+-.,[]"WpP[iPBsX#xx:POpsX#Jps(gU(y@?x)|v)]

First, the setup:

Oi:sX4Pp:#_FIa^C X^Y"><+-.,[]"
   sX4                          Space character, repeated 4 times
 i:                             Assign to i (the indent)
O                               Output it without trailing newline
                   Y"><+-.,[]"  Yank that string into y (BF commands)
                  ^             Split into a list of chars
                 X              Convert to a regex that matches any one of those chars
               C                Wrap the regex in a capture group
             a^                 Split the first program arg on that regex
                                (As in Python, splitting on a regex wrapped in a group
                                keeps the matches of the regex in the result list)
           FI                   Filter that list on this function:
         #_                      Length of item
                                (This gets rid of empty strings while not getting rid
                                of "0", which is falsey in Pip)
       p:                       Assign the result to p (the processed program)
      P                         Print it with trailing newline

Then, the main loop:

WpP[iPBsX#xx:POpsX#Jps(gU(y@?x)|v)]
Wp                                   While p is not empty:
  P                                  Print (with trailing newline)
   [                              ]  this list, concatenated:
                                      1. Indent:
          x                            Last chunk (initially empty string)
         #                             Length of the above
       sX                              That many spaces
     PB                                Push those onto the end of
    i                                  i (modifies i in-place)
                                      2. Current chunk:
             POp                       Pop the first item from p
           x:                          Assign it to x
                                      3. Padding:
                    p                  Remaining chunks
                   J                   Joined into a single string
                  #                    Length of the above
                sX                     That many spaces
                     s                4. One more space before explanation
                                      5. Explanation:
                          y@?x         Index of current chunk in BF commands
                                       (0 to 7, or nil if not found)
                        U(    )        Increment (1 to 8, or nil)
                               |       Logical OR
                                v      Negative one (1 to 8, or -1)
                      (g         )     Item in program args at that index
\$\endgroup\$
4
\$\begingroup\$

Stax, 45 bytes

å☺m█Q╗½§ÄN!╢┼♫mù}Jt→v)╢+Θ☼î╔↓bÉü╡⌐CÇ-♠•¿♪FM¥2

Run and debug it

Made this after Fmbalbuena's challenge in TNB. Will try to post something in Pip as well.

Found a nice save after Neil pointed out a mistake.

Explanation

LBc4NtPY{"><+-.,[]"XI^i*}/zsFs%Ntcaay%^(x_Iasn@as+4NtPs
LBc4NtPY{"                                              No Operation
          >                                             Move the pointer to the right
           <                                            Move the pointer to the left
            +                                           Increment the memory cell at the pointer
             -                                          Decrement the memory cell at the pointer
              .                                         Output the character signified by the cell at the pointer
               ,                                        Input a character and store it in the cell at the pointer
                [                                       Jump past the matching ] if the cell at the pointer is 0
                 ]                                      Jump back to the matching [ if the cell at the pointer is nonzero
                  "XI^i*}/zsFs%Ntcaay%^(x_Iasn@as       No Operation
                                                 +      Increment the memory cell at the pointer
                                                  4NtPs No Operation
\$\endgroup\$
4
  • \$\begingroup\$ Fails when the first character is a no-up, such as in the example provided... \$\endgroup\$
    – Neil
    Nov 19, 2021 at 11:10
  • \$\begingroup\$ I think i fixed it \$\endgroup\$
    – Razetime
    Nov 19, 2021 at 11:25
  • 1
    \$\begingroup\$ The 'explanation' in your answer doesn't help much if it's just a copy-paste of the output and not really explaining how the Stax program works. ;) \$\endgroup\$ Nov 19, 2021 at 16:19
  • 4
    \$\begingroup\$ comedy has been achieved \$\endgroup\$
    – Razetime
    Nov 20, 2021 at 3:30
4
\$\begingroup\$

Jelly, 46 bytes

®Ṭ€z0aḷżṚ⁸ṭṚŻ€4¡o⁶ð“><+-.,[]”iⱮxṠoŒQÄ©ŒQƲ$$ịŻ€

Try it online!

Program on the left and list of descriptions on the right. I suspect this can lose a few bytes fairly easily, but I'm posting it now in order to preserve one of the most hideous things I have ever written for posterity.

\$\endgroup\$
5
  • 2
    \$\begingroup\$ You forgot to print the code in the top of line (almost any forgot to print) :/ \$\endgroup\$
    – Fmbalbuena
    Nov 20, 2021 at 3:38
  • 1
    \$\begingroup\$ @Fmbalbuena nowhere in the challenge does it say the first line of output must be the full given code. \$\endgroup\$
    – Mayube
    Nov 20, 2021 at 4:05
  • 2
    \$\begingroup\$ @Mayube yes! see the testcases and the code. \$\endgroup\$
    – Fmbalbuena
    Nov 20, 2021 at 13:08
  • 4
    \$\begingroup\$ @Fmbalbuena Rules should not be inferred from the test cases, they should be explicitly stated in the rules of the challenge. \$\endgroup\$
    – Mayube
    Nov 21, 2021 at 17:10
  • \$\begingroup\$ @Mayube wdym??? \$\endgroup\$
    – Fmbalbuena
    Nov 21, 2021 at 17:16
2
\$\begingroup\$

C (clang), 191 188 bytes

-3 thanks to @ceilingcat

*S="><+-.,[]";i;l;d;f(char*a,**e){for(i=5,l=printf("    %s \n",a);*a;printf("%*s%s\n",l-i,"",e[d?d-(int)S:8]))for(d=index(S,*a),printf("%*c",i++,*a);!d&!index(S,*++a);)i+=write(1,a,*a>0);}

Try it online! Assumes explanations are in the order > < + - . , [ ] NOP.

\$\endgroup\$
6
  • \$\begingroup\$ fails when the length is 255~ but nevermind. \$\endgroup\$
    – Fmbalbuena
    Nov 20, 2021 at 2:09
  • 1
    \$\begingroup\$ ok! \$\endgroup\$
    – Fmbalbuena
    Nov 20, 2021 at 3:37
  • 1
    \$\begingroup\$ As far as I can tell that is due to TIO output truncation; I diff'd my output with the 05AB1E answer given that input and they're identical except for 1 trailing whitespace. \$\endgroup\$ Nov 20, 2021 at 3:51
  • \$\begingroup\$ You may use backticks if backticks is shorter than ident of spaces. \$\endgroup\$
    – Fmbalbuena
    Nov 23, 2021 at 17:19
  • 1
    \$\begingroup\$ @Fmbalbuena thanks but using backticks would cost 14 bytes (12 to print the trailing ``` and 2 to modify the value of l to match up) \$\endgroup\$ Nov 24, 2021 at 2:54
2
\$\begingroup\$

Perl 5 + -nF -M5.10.0, 93 bytes

Thanks to @Fmbalbuena for noticing I'd missed printing the unaltered string! Thanks to @emanresu A and @Neil's regex wizardry saving 14 (!!) bytes!

@@=<>;say$"x4,$_,map$"x(4+$-).$_.$"x(@F-($-+=y///c)).$@[index'><+-.,[]',$_],/[^]<>+-.[
]+|./g

Try it online!

Explanation

First print 4 x (string repetition operator) $" (which defaults to space), then store the length of the input (implicitly obtained via -F into @F) in $=, and store the explanations in @@. Then, for each matching piece of code (either one char from -<>+.,[], or any number of any other chars) concatenate the following with $\: 4 + $- (which starts out as 0) x $", followed by the piece of code ($_), followed by the original length, minus $- (to which we add the current length of this code piece) x $" and finally, using the index function on the string ><+-.,[], the message associated to the current piece of code (index returns -1 for missing index, for which Perl returns the last element of the list). $_ is output followed by $\ thanks to -p flag.


Perl 5 + -F/([^]<>+-.[]+|.)/ -M5.10.0, 92 bytes

The regex used in the above, can be provided via -F to save a byte.

$==y///c;@@=<>;say$"x4,$_,map/./&&$"x(4+$-).$_.$"x($=-($-+=y///c)).$@[index'><+-.,[]',$_],@F

Try it online!

\$\endgroup\$
8
  • \$\begingroup\$ You forgot to print code. \$\endgroup\$
    – Fmbalbuena
    Nov 19, 2021 at 13:12
  • \$\begingroup\$ And fails when the last is NOP. ([]<>{}()) \$\endgroup\$
    – Fmbalbuena
    Nov 19, 2021 at 13:15
  • \$\begingroup\$ @Fmbalbuena fixed, sorry about that! \$\endgroup\$ Nov 19, 2021 at 13:30
  • 1
    \$\begingroup\$ +-. works in the regex to save some bytes \$\endgroup\$
    – emanresu A
    Nov 19, 2021 at 23:40
  • 1
    \$\begingroup\$ Actually the regex can be simplified to /[^]<>+-.[ ]+|./g. (In case the comment gets mangled, there's a newline between the second pair of [].) \$\endgroup\$
    – Neil
    Nov 20, 2021 at 0:49
2
\$\begingroup\$

Ruby, 118 bytes

c,*a=*$<
print" "*l=4,c
c.scan(/[^+-.\[\]<>\n]+|./){puts" "*l+$&+" "*(c.size+4-l+=$&.size)+a["><+-.,[]".index($&)||8]}

Try it online!

Explanation

c,*a=*$<                                 # assign 'c' to first line and 'a' to the rest of input
print" "*l=4,c                           # output code and assign 'l' to 4 at the same time
c.scan(/[^+-.\[\]<>\n]+|./)              # for every match:
{puts" "*l+$&+" "*(c.size+4-l+=$&.size)+ # output the padded match, update 'l' to keep track of the spaces
a["><+-.,[]".index($&)||8]}              # output the correct explanation

inspiration taken from lonelyelk

\$\endgroup\$
1
  • \$\begingroup\$ +1 less bytes. good (+1 is a upvote) \$\endgroup\$
    – Fmbalbuena
    Nov 27, 2021 at 0:44
2
\$\begingroup\$

Haskell, 330 bytes

import Data.List
main=interact$unlines.f.lines
f(c:a)=(s 4++c):(map(p c.(a%)).tail.inits.t)c
t r|r==[]=[]|q x=[x]:t y|0<1=a:t b where(x:y)=r;(a,b)=span(not.q)r
q=(`elem`n)
p c(a,e)=s(4+(l.init)a)++last a++s(1+length c-l a)++e
s=(`replicate`' ')
n="><+-.,[]"
e%j=(j,e!!(n#last j))
l=length.concat
(s:z)#c|[s]==c=0|z==[]=1|0<1=1+z#c

Try it online!

Explanation:

import Data.List
main=interact$unlines.f.lines
f(c:a)=(s 4++c):(map(p c.(a%)).tail.inits.t)c

import Data.List inits(prefixes), this is used to keep track of the number of spaces
call f with lines of input, then output each line
f = " " * 4 + code +
map on each prefix of each command or noop:
select explanation and then pad with spaces

t r|r==[]=[]|q x=[x]:t y|0<1=a:t b where(x:y)=r;(a,b)=span(not.q)r
q=(`elem`n)
n="><+-.,[]"

t splits the code into commands or noop
q checks if a char is a command
n is the string "><+-.,[]"

p c(a,e)=s(4+(l.init)a)++last a++s(1+length c-l a)++e
s=(`replicate`' ')
e%j=(j,e!!(n#last j))
(s:z)#c|[s]==c=0|z==[]=1|0<1=1+z#c

p pads a (prefix, explanation)-pair with spaces
s returns n spaces
% creates said (prefix, explanation)-pair
# returns the index of the command in n or 8 if it's a noop

\$\endgroup\$
2
  • \$\begingroup\$ Hi, Natte! Fourth line of your program can be shortened to t(x:y)|q x=[x]:t y|(a,b)<-break q$x:y=a:t b;t[]=[], saving 16 bytes. \$\endgroup\$
    – Radek
    Nov 29, 2021 at 18:36
  • \$\begingroup\$ Furthermore, you can remove the Data.List import and rewrite line 3 as follows: f(c:a)=(s 4++c):(map(p c.(a%)).scanl1(++))[[x]|x<-t c], saving 8 bytes. \$\endgroup\$
    – Radek
    Nov 29, 2021 at 18:37
1
\$\begingroup\$

Retina, 207 205 bytes

*\K````
*\0G`
\G.
$& $&¶
m,-10T`_><+\-.,[]p`d`.$
(?<=^(.+¶)*.) 9¶(?=. 9)

+`^(.+¶)*(.)+ .¶(?!¶|(?<-2> )*(?(2)$))
$& 
,-10P`.+
+`(\d)( *)(¶(.+¶)*¶)
$2$1*_$3
+`(_)+(¶(.+¶)*(?<-1>.*¶)+(.*))
$4$2
(¶.*){9}$
```

Try it online! Explanation:

*\K````

Output the leading fence.

*\0G`

Output the line of code.

\G.
$& $&¶

Split the line of code into characters. For each character, repeat it twice, joined with a space.

m,-10T`_><+\-.,[]p`d`.$

Transliterate the last character of each pair to a digit 1-8, with unknown characters becoming 9.

(?<=^(.+¶)*.) 9¶(?=. 9)

Join together all runs of unknown characters.

+`^(.+¶)*(.)+ .¶(?!¶|(?<-2> )*(?(2)$))
$& 

(note trailing space) Align each run after the end of the previous run.

,-10P`.+

Pad all of the explanation lines to the same width.

+`(\d)( *)(¶(.+¶)*¶)
$2$1*_$3

Convert all of the transliterated digits to runs of underscores and move them to the end of the line.

+`(_)+(¶(.+¶)*(?<-1>.*¶)+(.*))
$4$2

Replace each run of underscores with the appropriate line of explanation.

(¶.*){9}$
```

Replace all of the explanations with the trailing fence.

\$\endgroup\$
1
\$\begingroup\$

Ruby, 143 142 138 bytes

->i{c,*e=i.split ?\n;puts" "*(m=4)+c;c.scan(/[^<>+-.,\[\]]+|./){|b|r=" ".*c.size+5;r[m,z=b.size]=b;puts r+e["<>+-.,[]".index(b)||8];m+=z}}

Try it online!

The function expects input to be lines separated by \n. It scans the program using regexp /[^<>+\.,\[\]-]+|./. It outputs to stdout instead of returning. Which is all there is to it.

Thanks to @Fmbalbuena for removing a space!

Saved 4 more bytes by re-reading ruby golfing tips :)

\$\endgroup\$
1
1
\$\begingroup\$

Haskell, 235 231 bytes

import Data.List
main=interact$unlines.map("    "++).f"><+-.,[]".lines
f o(i:e)|let t=not.(`elem`o);p=(>>" ").concat;w((c:r):t)=(c:r++p t++' ':maybe(e!!8)id(lookup c$zip o e)):[p[c:r]++l|l<-w t];w[]=[]=i:w(groupBy(\x y->t x&&t y)i)

Try it online!

Old answer:

Haskell, 235 bytes

import Data.List
main=interact$unlines.map("    "++).f.lines
o="><+-.,[]"
t=not.(`elem`o)
p=(>>" ").concat
f(i:e)|let w((c:r):t)=(c:r++p t++' ':maybe(last e)id(lookup c$zip o e)):[p[c:r]++l|l<-w t];w[]=[]=i:(w$groupBy(\x y->t x&&t y)i)

Try it online!

The answer is not final, will probably be golfed further.

\$\endgroup\$
3
  • \$\begingroup\$ bounty almost starts. but i don't bounty too fast. \$\endgroup\$
    – Fmbalbuena
    Nov 29, 2021 at 22:42
  • \$\begingroup\$ "The answer is not final, will probably be golfed further." Waits for 3 months. \$\endgroup\$
    – Fmbalbuena
    Jan 21, 2022 at 21:47
  • \$\begingroup\$ @Fmbalbuena Hey, I’m sorry you had to wait for that long; I have somehow missed the notification about your comment and assumed the answer go no interest whatsoever… Then I got sick and forgot all about it. Plus, I did not expect any bounties here. What is the possible bounty? Is it still within a reach? \$\endgroup\$
    – Radek
    Jan 23, 2022 at 1:02

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.