brainfuck, 811775709 701 bytes
>>-[-[--->]<<-]>--...<++++++++++.>>>>>>>>>>+[->>>>>>+<<,[[-<+<+>>]<----------[[-]->]-[+>-]<<<+[-<[-]>]<[-<+>>+<]++++++[->-------<]>>++++++[-<<++++++>>]>+<<-[<->>]>[<]<-[<------->>]>[<]<-[<-->>]>[<]<-[<-------->>]>[<]<--------------[<--->>]>[<]<--[<---->>]>[<]+++++[-<------>]<+[<----->>]>[<]<--[<------>>]>[<]<[-]<<+>[>>>-]<[>>>]<<<->>++++[->++++++++<]+>>>+>]<]+[<<<<<<]>>>>[.>>>>>>]>>>>-<<<<<<<<<<[<<<<<<]<<<<<.>>>+[->>>>>>>>>>>>>[>>>>>>]<<<<<[<[<<.<<<<]>>>>>>[>>>>>>]<<<<<.>>>>[>+>>>>>[<<<<.>>>>>+>>>>>]<<<<[>>>.>>>]>>>.>>>[.>>>>>>]>>[<<<<<<]<]<<<[>->>>+>[>>>.>>>]>>-<<<<<.<[<<<<<<]<[->>>>>>>[>>>>>>]+>>>>[>>>>>>]>>-<<<<<<[<<<<<<]<]>+[>>>>>>]+>>>>[.>>>>>>]>>[<<<<<<]<<<<]+<<<.>>]>]>[<<<<<<]<<<<<<...
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...
###### 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.