Search

Affichage des articles dont le libellé est interpreter. Afficher tous les articles
Affichage des articles dont le libellé est interpreter. Afficher tous les articles

jeudi 9 février 2017

Self-interpreter: Bonus - Enhancements (10)

Context

Wait, wait, we said the interpreter was over, didn't we?
Well, yes, we have a working interpreter that uses 1137 instructions, and performs about 80k operations for one interpreted instruction.
We can do better !

The most expensive part of our interpreter is memory accesses: moving all items (blocks of 2 cells) to the left, then back to the right, takes time, moreover it's used by all operations but > and <.
The second most expensive part we can improve is the instruction access, for the same reason.
Let's see how we can get rid of instruction and memory pointers

Global idea: here is what our memory map should be
0 {instr_before} 0 0 {instr_after} 0/bit_else 1/instruction_copy inactive_counter direction_flag 0 0 {mem_before} 0 0 {mem_after}
No pointer here. But 2 arrays for instructions and same for memory.
Then, as a convention, we can define current instruction or current memory cell as the first item in the corresponding x_after array.
Thanks to this:
  • Instruction can be accessed more quickly (no need to move X items back and forth)
  • Same for memory
  • Drawbacks
    • Incrementing the instruction pointer is done by moving one element from one array to the other
    • And instructions > and < are executed by moving one memory cell from one array to the other
  • However, these drawbacks are nothing compared to the improvement of fetch or all other operations
One last thing we can improve is the evaluation of while and loop. There is no real need to copy memory cell outside the memory array to evaluate it. It was a quick and dirty way to have all our variables closed together, but it's actually useless as we can directly check the value at it's original location.

Initial state

  • Memory: empty
  • Cursor: on first cell
  • Input: code to execute#input of interpreted program

Code (minified - only 695 instructions - 38% shorter !!!)

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

Code - with comments, character codes and memory map

separator 35/0
inc 43/8
read 44/9
dec 45/10
print 46/11
left 60/25
right 62/27
while 91/56
loop 93/58

0 {instructions_before} 0 0 {instructions_after} 0 1/instruction_copy inactive_counter direction_flag 0 0 {memory_before} 0 0 {memory_after}

>>>,[>+++++[-<------->]+<[>,>]>[->]<<]>+
[-
  fetch
  <<[<]>[-<+<+>>]<[->+<]<[->>[>]>+<<[<]<]>>[>]
  inactive check processing
  +>>[
    check instruction
    <<++++++[->--------<]+>
    [
      --
      [
        not while nor loop
        reset else bit
        <->[-]
      ]<[-
        instruction loop
        decrease inactive_counter
        >>-<<
      ]>
    ]<[-
      instruction while
      increase inactive_counter
      >>+<<
    ]>
    reset instruction (but not null)
    +
    >[
      if counter is not null
      copy inactive_counter (to break loop)
      <->[->>+<<]
    ]<[
      else reset direction_flag
    ->>-<<
    ]+>
  ]
  reload inactive_counter
  >>[-<<+>>]<<
  <[
    Execute instructions
    --------
    [
      -
      [
        -
        [
          -
          [
            <+[->-------<]+>
            [
              --
              [
                <+++[->-------<]+>-
                [
                  --
                  [
                    not an instruction
                    <->[-]
                  ]<[-
                    instruction loop
                    >>>>>>[>>]>>>[<<<<<[<<]<+<->>>>[>>]>]<[<<]<<[<<]>[-<<<+>>>]<<<<<
                  ]>
                ]<[-
                  instruction while
                  >>>>>+>[>>]>>>[<<<<<[<<]>->[>>]>]<[<<]<<[<<]>[-<<<+>>>]<<<<<
                ]>
              ]<[-
                instruction right
                >>>>>>[>>]+>>[-]>[-<<+>>]>[-]+<<<<[<<]<<<<
              ]>
            ]<[-
              instruction left
              >>>>>>[>>]+<[->>+<<]<[-]<<[<<]<<<<
            ]>
          ]<[-
            instruction print
            >>>>>>[>>]>>>.<<<<<[<<]<<<<
          ]>
        ]<[-
          instruction dec
          >>>>>>[>>]>>>-<<<<<[<<]<<<<
        ]>
      ]<[-
        instruction read
        >>>>>>[>>]>>>,<<<<<[<<]<<<<
      ]>
    ]<[-
      instruction inc
      >>>>>>[>>]>>>+<<<<<[<<]<<<<
    ]>
    clear instruction
    [-]
    <
  ]
  >>>>+<
  check direction
  [
    <<<<[<]<<[->>+<<]>>[>]>>>>-
  ]>
  [
    -<<<<<[<]>[-<<+>>]>[>]>>>>>
  ]
  <<<<<
  <[<]>[[>]>+<]>
]

Final state

  • Memory: see above
  • Cursor: on instruction, next to inst_ptr
  • Input: unchanged
  • Output: unchanged

Example

Live BF Interpreter.
Be careful not to execute too long code: one single BrainFuck operation to interpret is rather long (parsing, processing, ...). This version still allows you to run longer code (in theory).
With the same code sample than previous interpreter version:
,.---.>+++[-<++>]<+..+++.>++++[->++[->++++<]<]>>.<<<[->+>+<<]++[->++++<]>.>.+++.<<++[->-----<]>-.<++[->----<]>.#H
This new interpreter can execute the code in only 13.675.534 operations, which is really good compared to the 65.622.379 operations for same input using previous interpreter code !!!
This gives an average ratio of 16.107 operations to execute one instruction.
Again, this ratio is definitely not accurate, as operations are also used to read program, or in inactive mode, ... but it's still a good indicator of execution complexity.


Conclusion: this num interpreter needs 38% less code, and is 5x faster to execute !

Back to previous step
(No more next step this time 😅)

mercredi 8 février 2017

Self-interpreter: completed (9)

Context

We now have all our pieces in place. Let's run our interpreter !

Initial state

  • Memory: empty
  • Cursor: on first cell
  • Input: code to execute#input of interpreted program

Code (minified - only 1137 instructions)

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

Code - with comments, character codes and memory map

separator 35/0
inc 43/8
read 44/9
dec 45/10
print 46/11
left 60/25
right 62/27
while 91/56
loop 93/58


0 0 0 {instructions} 0 IP current bit_else 0 inactive_flag direction_flag memory_pointer MPcopy/read_value 0 0 0 0 0 {memory}

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

  parse current instruction
  >+<[>->+>
  [
    check if instruction is while (increase flag) or loop (decrease flag)
    <<++++++++[-<------->]+<
    [
      --
      [>-<
        clear instruction read
        [-]
      ]>[-
        instruction: loop
        decrease inactive_flag
        >>-<<
      ]<
    ]>[-
      instruction: while
      increase inactive_flag
      >>+
   
      if inactive_flag = 0 then reset direction_flag
      [<-]<[>>[-]<<-<]>+
      <
    ]<
    set instruction read to 1 to consider it as a non instruction
    +>>-
  ]
  <[-<]
  +<
  [
    --------
    [
      -
      [
        -
        [
          -
          [
            >+[-<------->]+<
            [
              --
              [
                >+++[-<------->]+<-
                [
                  --
                  [
                    not an instruction
                    >-<[-]
                  ]>[-
                    instruction: loop
                  duplicate memory pointer and move to copy
                  >>>>[->+>+<<]>>[-<<+>>]<+
                  [
                    -
                    go to next memory cell
                    >>>>[>>]+>>
                    (re)init/copy memory indicator
                    [-]>[-<<+>>]<<<[<<]<<
                  ]
                  go to current memory cell and copy value
                  >>>>[>>]<[->>+<<]>>[-<<+<[<<]<<+>>>>[>>]>]<<
                  move array back to initial position
                  <[[->>+<<]>[->>+<<]<<<]
                  <<
                  check current memory value: do nothing if null and move to corresponding while otherwise
                  [
                    if not null (current position: next to memory_pointer)
                    reset value
                    [-]
                    decrease inactive_flag and change direction_flag
                    <<<->[-]+>>
                  ]
                  <<<<<
                  ]<
                ]>[-
                  instruction: while
                  duplicate memory pointer and move to copy
                  >>>>[->+>+<<]>>[-<<+>>]<+
                  [
                    -
                    go to next memory cell
                    >>>>[>>]+>>
                    (re)init/copy memory indicator
                    [-]>[-<<+>>]<<<[<<]<<
                  ]
                  go to current memory cell and copy value
                  >>>>[>>]<[->>+<<]>>[-<<+<[<<]<<+>>>>[>>]>]<<
                  move array back to initial position
                  <[[->>+<<]>[->>+<<]<<<]
                  check current memory value: do nothing if not null and move to corresponding loop otherwise
                  <+<[>-<[-]]>[-<
                    if null (current position: next to memory_pointer)
                    increase inactive_flag
                    <<<+>>>
                  >]<
                  <<<<<
                ]<
              ]>[-
                instruction: right
                >>>>+<<<<
              ]<
            ]>[-
              instruction: left
              >>>>-<<<<
            ]<
          ]>[-
            instruction: print
            duplicate memory pointer and move to copy
            >>>>[->+>+<<]>>[-<<+>>]<+
            [
              -
              go to next memory cell
              >>>>[>>]+>>
              (re)init/copy memory indicator
              [-]>[-<<+>>]<<<[<<]<<
            ]
            go to current memory cell and print value
            >>>>[>>]<.
            move array back to initial position
            <[[->>+<<]>[->>+<<]<<<]
            <<<<<<<
          ]<
        ]>[-
          instruction: dec
          duplicate memory pointer and move to copy
          >>>>[->+>+<<]>>[-<<+>>]<+
          [
            -
            go to next memory cell
            >>>>[>>]+>>
            (re)init/copy memory indicator
            [-]>[-<<+>>]<<<[<<]<<
          ]
          go to current memory cell and decrease value
          >>>>[>>]<-
          move array back to initial position
          <[[->>+<<]>[->>+<<]<<<]
          <<<<<<<
        ]<
      ]>[-
        instruction: read

        duplicate memory pointer and move to copy
        >>>>[->+>+<<]>>[-<<+>>]<+
        [
          -
          go to next memory cell
          >>>>[>>]+>>
          (re)init/copy memory indicator
          [-]>[-<<+>>]<<<[<<]<<
        ]
        go to current memory cell and read value
        >>>>[>>]<,
        move array back to initial position
        <[[->>+<<]>[->>+<<]<<<]
        <<<<<<<
      ]<
    ]>[-
      instruction: inc
      duplicate memory pointer and move to copy
      >>>>[->+>+<<]>>[-<<+>>]<+
      [
        -
        go to next memory cell
        >>>>[>>]+>>
        (re)init/copy memory indicator
        [-]>[-<<+>>]<<<[<<]<<
      ]
      go to current memory cell and increase value
      >>>>[>>]<+
      move array back to initial position
      <[[->>+<<]>[->>+<<]<<<]
      <<<<<<<
    ]<
    increase or decrease instruction counter based on direction_flag
    >>>>[-<<+<<<-->>>>>]<<[->>+<<]<<<++>
  ]]>[[-]<<[-]>>]<
<]

Final state

  • Memory: see above
  • Cursor: on instruction, next to inst_ptr
  • Input: unchanged
  • Output: unchanged

Example

Live BF Interpreter.
Be careful not to execute too long code: one single BrainFuck operation to interpret is rather long (parsing, processing, ...)
Here is a quick code sample. With an input H, it prints HELLO WORLD. You can use this code to test interpreter. input will be:
,.---.>+++[-<++>]<+..+++.>++++[->++[->++++<]<]>>.<<<[->+>+<<]++[->++++<]>.>.+++.<<++[->-----<]>-.<++[->----<]>.#H
Note: the code above can be executed directly through less than 850 operations (849 exactly).
The interpreted version can be executed in 65.622.379 operations !!!
This gives an average ratio of 77.924 operations to execute one instruction.
This ratio is definitely not accurate, as operations are also used to read program, or in inactive mode, ... but it's still a good indicator of execution complexity.


Back to previous step
Go to next step (next step ? really ? I said it works, didn't I?)

Self-interpreter: inactive and direction flags (8)

Context

As a reminder, here is the final memory map
0 0 0 {instrs} 0 inst_ptr 0 0 0 inactive_flag direction_flag memory_ptr  0 0 0 0 0 {mem}
Let's now see how to handle inactive_flag and direction_flag
  • Inactive_flag: when not null, execution of instructions should be suspended, only '[' and ']' will taken into account.
  • While will increase the inactive_flag and if null update direction_flag.
  • Loop will decrease the inactive_flag

Initial state

  • Memory: see above
  • Cursor: on instruction, next to inst_ptr
  • Input: any

Process

  • Set else bit
  • If inactive flag is not null
    • Reset else bit  
    • Check if instruction is ]
      • Decrease inactive flag
    • If not, check if it is [
      • Increase inactive flag
      • If inactive flag is null, reset direction flag
    • If not, ignore instruction
  • If not null: process instruction normally 
Update instruction pointer: instead of  moving to next instruction, we need to check if direction_flag is not null (go back) or not (go forward). An easier way to do that is to go back to steps back if direction flag is not null, and then go one step forward in all cases.

Code snippet for inactive case

parse current instruction
>+<[>->+>
[
  check if instruction is while (increase flag) or loop (decrease flag)
  <<++++++++[-<------->]+<
  [
    --
    [>-<
      clear instruction read
      [-]
    ]>[-
      instruction: loop
      decrease inactive_flag
      >>-<<
    ]<
  ]>[-
    instruction: while
    increase inactive_flag
    >>+
  
    if inactive_flag = 0 then reset direction_flag
    [<-]<[>>[-]<<-<]>+
    <
  ]<
  set instruction read to 1 to consider it as a non instruction
  +>>-
]
<[-<]
+<
[
 
and continue

Code snippet for instruction pointer update

increase or decrease instruction counter based on direction_flag
>>>>[-<<+<<<-->>>>>]<<[->>+<<]<<<++>

Final state

  • Memory: see above
  • Cursor: on instruction, next to inst_ptr
  • Input: unchanged
  • Output: unchanged

Self-interpreter: while and loop (7)

Context

As a reminder, here is the final memory map
0 0 0 {instrs} 0 inst_ptr 0 0 0 inactive_flag direction_flag memory_ptr  0 0 0 0 0 {mem}
Let's now see how to implement '[' and ']'

Initial state

  • Memory: see above
  • Cursor: on instruction, next to inst_ptr
  • Input: any

Process

  • Common part 
    • Access memory value - see previous post
    • Copy memory value
    • Move array back to initial position
  •  While '['
    • If copy is null
      • increase inactive_flag
    • If not: do nothing
  • Loop ']'
    • If copy is null: do nothing
    • If not:
      • decrease inactive_flag
      • Set direction_flag to 1

Code for while

Access memory cell
>>>>[->+>+<<]>>[-<<+>>]<+[->>>>[>>]+>>[-]>[-<<+>>]<<<[<<]<<
]
>>>>[>>]<
copy value
[->>+<<]>>[-<<+<[<<]<<+>>>>[>>]>]<<

move array back to initial position
<[[->>+<<]>[->>+<<]<<<]

check current memory value: do nothing if not null and move to corresponding loop otherwise
<+<[>-<[-]]>[-<
  if null (current position: next to memory_pointer)
  increase inactive_flag
  <<<+>>>
>]<
<<<<<

Code for loop

duplicate memory pointer and move to copy
>>>>[->+>+<<]>>[-<<+>>]<+
[
  -
  go to next memory cell
  >>>>[>>]+>>
  (re)init/copy memory indicator
  [-]>[-<<+>>]<<<[<<]<<
]
go to current memory cell
>>>>[>>]<
copy value
[->>+<<]>>[-<<+<[<<]<<+>>>>[>>]>]<<
move array back to initial position
<[[->>+<<]>[->>+<<]<<<]
check current memory value: do nothing if null and move to corresponding while otherwise
<<
[
  if not null (current position: next to memory_pointer)
  reset value
  [-]
  decrease inactive_flag and change direction_flag
  <<<->[-]+>>
]
<<<<<

Final state

  • Memory: see above
  • Cursor: on instruction, next to inst_ptr
  • Input: unchanged
  • Output: unchanged

Self-interpreter: memory access, inc, dec, print and read (6)

Context

As a reminder, here is the final memory map
0 0 0 {instrs} 0 inst_ptr 0 0 0 inactive_flag direction_flag memory_ptr  0 0 0 0 0 {mem}
Let's now how to access a value in memory and implement '+', '-', '.' and ',' operations.

Initial state

  • Memory: see above
  • Cursor: on instruction, next to inst_ptr
  • Input: any

Process

  • Access memory value
    • Copy memory pointer, and add one to copy
    • While copy is not null
      • Move through the first memory array (initially: none)
      • Move second memory array (initially: whole memory) first element to end of first array. Init / reinit first cell of the block to 1.
      • Decrease copy
      • Loop invariant: target item is Nth of second memory array, where N is the actual copy value (1-based index)
      • When copy is null: N is the last element of first memory array
    • Go to last element of first memory array
    • Execute instruction
    • Move array back to initial position

Code - generic

duplicate memory pointer and move to copy
>>>>[->+>+<<]>>[-<<+>>]<+
[
  -
  go to next memory cell
  >>>>[>>]+>>
  (re)init/copy memory indicator
  [-]>[-<<+>>]<<<[<<]<<
]
go to current memory cell
>>>>[>>]<
do somethingmove array back to initial position
<[[->>+<<]>[->>+<<]<<<]
<<<<<<<

Code (minified) for inc

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

Code (minified) for dec

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

Code (minified) for print

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

Code (minified) for read 

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

Final state

  • Memory: see above
  • Cursor: on instruction, next to inst_ptr
  • Input: unchanged
  • Output: unchanged

Self-interpreter: Memory map, left and right (5)

Context

Here is the final memory map will be
0 0 0 {instrs} 0 inst_ptr 0 0 0 inactive_flag direction_flag memory_ptr  0 0 0 0 0 {mem}
The inactive_flag will be updated by both '[' and ']', and used by the global instruction processing to do execute or not instructions.
The direction flag will be used by the fetch to move on left, or right, and updated by ']' instruction when active, or '[' when inactive.

Let's now implement < and > operations.

Initial state

  • Memory: see above
  • Cursor: on instruction, next to inst_ptr
  • Input: any

Process

  • Increase or decrease memory_ptr

Code for >

  >>>>+<<<<

Code for <

  >>>>-<<<<

Final state

  • Memory: see above
  • Cursor: on instruction, next to inst_ptr
  • Input: unchanged
  • Output: unchanged

Self-interpreter: switch on instruction code (4)

Context

Implementing a switch in BrainFuck is not that complex: case values should be sorted (a bit easier to implement), and then
  1. Set else bit to 1
  2. Decrease the switch condition by first case value
    1. If not null, continue with other values
    2. If null, go to else bit, reset and process
  3. Finally, go to else bit and process the default case

Initial state

  • Memory: 0, 0, 0, instructions, 0, IP, instruction
  • Cursor: on instruction
  • Input: any

Process

  • Set else bit to 1
  • Execute the switch
    • Decrease current condition with total decrease (since beginning) equals to the current case
    • Value is not null: process next case
    • Value is null: process current case, reset else bit
The global syntax looks like
[
  set else bit
  >+<
  decrease condition
  [
    next case : decrease condition
    ...
  ]>[- reset else bit
    current case implementation
  ]<
]

Note: for test purposes, the current instruction processing will be a simple display on output of the current instruction, and reset. Non-instruction characters will be simply ignored

Code 

  parse current instruction
  [
    >+<--------
    [
      -
      [
        -
        [
          -
          [
            >+[-<------->]+<
            [
              --
              [
                >+++[-<------->]+<-
                [
                  --
                  [
                    not an instruction
                    >-<[-]
                  ]>[-
                    instruction: loop
                    +++++++++[-<++++++++++>]<+++.[-]>
                  ]<
                ]>[-
                  instruction: while
                  +++++++++[-<++++++++++>]<+.[-]>
                ]<
              ]>[-
                instruction: right
                ++++++++[-<++++++++>]<--.[-]>
              ]<
            ]>[-
              instruction: left
              ++++++++[-<++++++++>]<----.[-]>
            ]<
          ]>[-
            instruction: print
            ++++++[-<+++++++>]<++++.[-]>
          ]<
        ]>[-
          instruction: dec
          ++++++[-<+++++++>]<+++.[-]>
        ]<
      ]>[-
        instruction: read
        ++++++[-<+++++++>]<++.[-]>
      ]<
    ]>[-
      instruction: inc
      ++++++[-<+++++++>]<+.[-]>
    ]<

Code (minified)

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

Final state

  • Memory: 0,instructions, 0, IP, 0
  • Cursor: on last 0
  • Input: unchanged
  • Output: unchanged (except for test purposes)
Note: the root switch will be modified in the future, to take incative_flag into account

Example

Live 'Instruction read / fetch / switch / print' example, that reads code to execute until it reaches separator, then use the fetch loop to get instructions, switch on different instructions, then display instructions using fetch loop. By the way, it's also a nice 'BrainFuck code minifier' - implemented in BrainFuck!

Back to previous step
Go to next step

Self-interpreter: fetch instructions (3)

Context

Now that our instructions are read correctly, let's implement a fetch loop.
Given an instruction pointer, the fetch is an operation that gives us the operation to implement.
This is globally similar to a random access in array, but the fetch loop should be executed again and again. Then, it's simpler to ave a one-based index: we can loop on fetch pointer in this case.
And access an element in this case is not complex either, the pointer value just need to be decreased by a given offset

Initial state

  • Memory: 0, 0, 0, instructions, 0, IP
  • Cursor: on instruction pointer (initially 0)
  • Input: any

Process

  • Set pointer to 1
  • While pointer is not null
    • Decrease pointer
    • Copy pointer
    • Use pointer copy to get instruction
    • Copy current instruction outside array - the instruction copy will be placed just beside the instruction pointer cell
    • If instruction is not null
      • For test purposes: rebuild original value and print it. This part will be later replaced by instruction processing, of course
      • Increase pointer back to original value
      • Increase pointer (move to next instruction)
    • Otherwise, break the loop

    Code 

    fetch loop
    +[
      get current instruction
      -[->+>+<<]>>[-<<+>>]<[-<<<[<]>[-<<+>>]>[>]>>]<<<[<]>[-<+<+>>]<[->>[>]>>+<<<[<]<]<[->>+<<]<[[->>+<<]<]>>>[>]>>

      parse current instruction
      >+<[>-<
        rebuild and print (test)
        >+++++[-<+++++++>]<.[-]
        increment instruction counter
        <++>
      ]>[-<<[-]>>]<
    <]

    Code (minified)

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

    Final state

    • Memory: 0,instructions, 0, IP
    • Cursor: on IP
    • Input: unchanged
    • Output: unchanged (except for test purposes)
    Note: when direction_flag will be available, the <++> line will be replaced by
        increment or decrement based on direction_flag

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

    Example

    Live 'Instruction reader / fetcher / printer' example, that reads code to execute until it reaches separator, then display the code using the fetch loop.

    Back to previous step
    Go to next step

    Self-interpreter: load code (2)

    Context

    Let's now implement the first part of our interpreter: read code to execute - and code only. The part dedicated to execution's input should remain unread for now.
    Here are the characters we want to read. First value is the corresponding ASCII code, second is 35 below those values (35 is ASCII code of #, our separator.
    • # separator 35/0
    • + inc 43/8
    • , read 44/9
    • - dec 45/10
    • . print 46/11
    • > left 60/25
    • < right 62/27
    • [ while 91/56
    • ] loop 93/58
    Our goal here will be to
    1. Read character
    2. Subtract 35 to check if character is the separator, or not
      1. If separator: stop reading
      2. If not: store instruction value (even the -35 one, it doesn't matter as long as '+' is now defined by 8, ', ' by 9, ...)

    Initial state

    • Memory: empty
    • Cursor: first cell
    • Input: any

    Process

    • Read char
    • While current char is not null
      • Decrease by 35, set else bit
      • If value is not null, read next char
      • Otherwise, stop reading

    Code

    >>>,
    leave some cells for future use and read char
    [
      >+++++[-<------->]+
      subtract by 35 and set else bit
      <[>,>]
      if not # then read next char
      >[[-]>]
      otherwise do not read and back to same position in both cases
      <<
    ]>

    Code (minified)

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

    Final state

    • Memory: 0, 0, 0, instructions, 0, IP (=0)
    • Cursor: on IP (instruction pointer, currently 0)
    • Input: remaining is interpreted code's input only
    • Output: unchanged

    Example
    Live 'Instruction reader' example, that reads code to execute until it reaches separator, then display the code (add 35 to each char and print).

    Back to previous step
    Go to next step

    Self-interpreter: introduction (1)

    Context

    Let's create a BrainFuck interpreter in BrainFuck. The goal of this post is to understand how we can do that, and then next ones will be about implementation.
    BrainFuck execution needs
    • Instructions (+ - . , < > [ ])
    • Instruction pointer
    • Input stream
    • Output stream
    • Memory
    • Memory pointer

    Instructions and input stream

    Here, as it's an interpreter, the instructions will come from the global input stream. Then, we need to make a distinction between code to interpret and input used by interpreted code.
    This can be done using a special character that will be forbidden in the code part.
    For this project, let's use # as our separator.

    Memory

    The memory needs to be a subset of global execution memory. But let's have it as big as possible: we'll use some space at the beginning of the memory array for instructions to execute (fixed size), and some variables / swap memory for our interpreter.
    Another issue with the memory is that it's an array. As we saw in previous posts, we can work with arrays, including array item random access, as long as values aren't null within the array. Or, one can have null values in its memory, this would prevent our code from reading the array.

    We can figure this out by at least 2 different ways:
    • We can have an offset (+1) on each memory cell. Then zeros will be ones, ones will be twos, ...
      • Pros: Quite easy to implement (our operations to read and write in memory array should just handle it)
      • Cons: now, 255 is a forbidden value
    • We can have 2 memory values for one array cell. Like one as value, one as is_null flag, or even just [1, value]: browsing array will be done by blocks of 2 cells, whatever the value is
      • Pros: all values are now allowed
      • Cons:
        • a bit more complex to implement
        • uses twice more space than memory
    Anyway: the final choice doesn't really matter, but the second option would be a good example of arrays allowing '0' values, and it's also better to avoid forbidden memory values.

    Instruction pointer

    The core of our program will be
    1. Read code to interpret
    2. While there is a current instruction
      1. execute it
      2. move to next instruction (except for '[' and ']', where it's not exactly the next instruction)
    Then, we need to loop on instruction fetches. let's loop on instruction pointer value, and then instruction array will have a one-based index (to avoid null pointer breaking the loop).

    Finally, to avoid complexity, [ and ] will be handled in a particular way
    • an inactive_flag will indicate if code can be run
    • direction_flag will indicate if instructions should be read from left to right or right to left
    • if inactive_flag is not null
      • Execution of '['
        • increase inactive_flag
        • if inactive_flag is now null, reset direction_flag
      • Execution of ']'
        • decrease inactive_flag
      • Execution of anything else
        • Do nothing
    • if inactive_flag is null
      • Execution of '['
        • If current memory value is not null: do nothing
        • If null:
          • increase inactive_flag
      • Execution of ']'
        • If current memory value is null: do nothing
        • If not null
          • Set direction_flag to 1
          • Decrease inactive_flag
    This way, the interpreter will read all instructions one by one. The inactive_flag will be used as a counter to know if we reached the right number of [ and ] to start execution back to normal.

    Global memory map and execution flow

    Memory map:
             0 0 0 instructions 0 instruction_pointer some_free_space memory_pointer 0 0 0 0 0 0 memory

    Execution flow
    1. Read instructions, i.e. read only instructions, and stop when # separator is read
    2. Fetch instruction given by instruction pointer
    3. Execute instruction
      • + and - : use memory_pointer to reach current cell in memory array and update value
      • < and >: update memory_pointer (+/- 1)
      • , : read in input stream the next char and store in memory cell pointed by memory_pointer
      • . : write current memory value to output stream
      • [ : read current memory value
        • if 0 : increase instruction pointer until it meets as many '[' than ']'
        • if not: do nothing
      • ] : read current memory value
        • if 0: do nothing
        • if not: decrease instruction pointer until it meets as many '[' than ']'