Trying to build a debugger in React, but can't figure out how to update state properly

Hello! I am attempting to build a BrainFuck debugger with React, but encountering problems with state because setState() is asynchronous, and not updating the state the way I want it to.

If you aren’t familiar with it, BrainFuck is an esoteric, minimalistic joke programming language, turing-complete, with no words, letters, variables or numbers, using only the following operators:

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

Basically, you start out with a ‘tape’, which you can think of as an array with 30,000 items, all of which are the number 0 to start out. You start at the start of your program, and the first position on the tape (tape[0]). The + operator increments the value on the tape by 1, the - operator decrements it by 1. The command > moves over to the cell on the right, the command < moves to the cell on the left. The . reads the current cell, and prints it’s ASCII value to the screen (so if you are on the first cell, and it’s value is 65 (0x41 in hex), it would print an A). Similarly, the , reads a single character from STDIN and writes it’s decimal value to that cell.

For example, if your program is

+++++>++++>+++>++>+

When you’re done, your tape looks like

[5, 4, 3, 2, 1, 0, 0, 0 …(etc)]

I already wrote a ton of code to load a program into memory and run the debugger, while showing you the value of the cells, and wound up having to scrap it for a refactor. The biggest crux of the issue is that I’m using the state to hold all of the debugger information, iterating through the loaded BF program with a loop, and setState() is asynchronous. So, as I iterate through the instructions, I’m attempting to update the state of both the tape, and where the current data pointer is. But, React puts all of these updates in a batch, and seems to only update once the loop is done, so it doesn’t work.

As I mentioned earlier, I pretty much scrapped everything and am refactoring entirely, so so far, I only have (broken) code for the + and - operators. Once I can get them to work the way I want, I’ll continue with the rest of the operators. (I’ve written a functional one in C++ here, but ignore the network() function, it’s for an extension I’m writing) Here’s the current React code:

App.js:

import React from 'react';
import Debugger from './debugger';
import './App.css';

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <p>
          The <code>BrainFuck</code> Debugger - version 0.0.1
        </p>
        <br />
        <p>
            Below, paste or type your brainfuck program into the input area. Hit the button to start it, and watch it populate the memory in the space below.
        </p>
        <br />
        <Debugger />
      </header>
    </div>
  );
}

export default App;

debugger.js:

import React, {Component} from 'react';
import TapeDisplay from './tape';
import './App.css';

class Debugger extends Component {
    constructor(props){
        super(props);
        this.state = { 
            program: "+++++", /* fixed for now, will load into memory via user input later */
            dp: 0, /* Data pointer, where on the tape we are */
            running: false, /* whether or not the debugger is running, will be used to implement breakpoints later */
            tape: new Array(30000).fill(0),
            debugging_memory: 5, /* Don't display all 30,000 cells at once, only the number of cells outlined in debugging memory. For now, that is a fixed value until we can update state somewhat-synchronously */
        }
    }

/* test program for when all operators are programmed:
++++++++++[>++++++++++>++++++++++>++++++++++>++++++++++<<<<-]>.>.>.>.>,>,>,>,>,<<<<<.>.>.>.>.
*/

    increment_tape = (index) => {
        var item = this.state.tape[index]
        item++;
        const newTape = [
            ...this.state.tape.slice(0, index),
            item,
            ...this.state.tape.slice(index+1)
        ]
        this.setState({
            tape: newTape
        }, () => {
            console.log(`incremented tape to ${item}`)
        });
    }

    decrement_tape = (index) => {
        var item = this.state.tape[index]
        item--;
        const newTape = [
            ...this.state.tape.slice(0, index),
            item,
            ...this.state.tape.slice(index+1)
        ]
        this.setState({
            tape: newTape
        }, () => {
            console.log(`decremented tape to ${item}`)
        });
    }

    run_debugger(){
        for(var i=0;i<this.state.program.length;i++){
            switch(this.state.program[i]){
                case '+':
                    this.increment_tape(this.state.dp);
                    break; 
                case '-':
                    this.decrement_tape(this.state.dp);
                    break; 
            }
        }
    }

    render(){
        return (<div>
            <div className="tapeMemoryDisplay">
                <TapeDisplay tape={this.state.tape} order={this.state.debugging_memory} />

            </div>
            <div className="debugOptions">
                <button id="run" onClick={() => {this.run_debugger()}}>Run</button>
            </div>
            <div><br />
            <textarea id="programInput" type="text" value={this.state.value} onChange={this.handleChange} />
            </div>            
        </div>
        )
    }
}

export default Debugger;    

tape.js:

import React, {Component} from 'react';

class TapeDisplay extends Component{

    render_cell(cell, index){
        return(<div style={{order: index}} key={index}>{cell}</div>)
    }

    render_cells(tape,order){
        var tapeArray = [];
        for(var i=0;i<order;i++){
            tapeArray.push(this.render_cell(tape[i], i))
        }
        return tapeArray;
    }

    render(){
        return(
            <div className="tapeDisplay">
                {this.render_cells(this.props.tape, this.props.order)}
            </div>
        )
    }
}

export default TapeDisplay;

How should I go about doing this? Should I scrap the for-loop and try doing something else altogether?

So, I’ve managed to get it to run properly without a for-loop, using componentDidUpdate, which I will update here for anyone with a similar problem with state. However, the problem I am now having is that the setTimeout() delay does delay/space out the execution a bit, but it seems like the when I first hit the run button, there’s the delay until it runs, but the delay is not consistent between updates. It increments to 1, then 3, then 6, without pausing and updating in a uniform way.

debug.js:

import TapeDisplay from './tape';
import './App.css';

class Debugger extends Component {
    constructor(props){
        super(props);
        this.state = { 
            program: "++++++", /* fixed for now, will load into memory via user input later */
            eip: 0, /* instruction pointer, to replace the broken for loop */
            dp: 0, /* Data pointer, where on the tape we are */
            running: false, /* whether or not the debugger is running, will be used to implement breakpoints later */
            tape: new Array(30000).fill(0),
            debugging_memory: 5, /* Don't display all 30,000 cells at once, only the number of cells outlined in debugging memory. For now, that is a fixed value until we can update state somewhat-synchronously */
        }
    }

/* test program for when all operators are programmed:
++++++++++[>++++++++++>++++++++++>++++++++++>++++++++++<<<<-]>.>.>.>.>,>,>,>,>,<<<<<.>.>.>.>.
*/

    increment_tape = (index) => {
        var item = this.state.tape[index]
        item++;
        const newTape = [
            ...this.state.tape.slice(0, index),
            item,
            ...this.state.tape.slice(index+1)
        ]
        this.setState({
            tape: newTape
        }, () => {
            console.log(`incremented tape to ${item}`)
        });
    }

    decrement_tape = (index) => {
        var item = this.state.tape[index]
        item--;
        const newTape = [
            ...this.state.tape.slice(0, index),
            item,
            ...this.state.tape.slice(index+1)
        ]
        this.setState({
            tape: newTape
        }, () => {
            console.log(`decremented tape to ${item}`)
        });
    }

    componentDidUpdate(){
        var that = this;
        setTimeout(function(){
            if(that.state.running === true){
                that.run_debugger();
            }
        }, 1000);
    }

    start_debugger(){
        this.setState({running:true})
    }

    run_debugger(){
        var eip = this.state.eip;
        console.log(`running debugger at eip: ${eip}`)
        switch(this.state.program[eip]){
            case '+':
                this.increment_tape(this.state.dp);
                break; 
            case '-':
                this.decrement_tape(this.state.dp);
                break; 
            default:
                break;
        }
        if(eip === this.state.program.length){
            this.setState({running: false});
        } else {
            this.setState({eip: eip+1});
        }
    }

    render(){
        return (<div>
            <div className="tapeMemoryDisplay">
                <TapeDisplay tape={this.state.tape} order={this.state.debugging_memory} />

            </div>
            <div className="debugOptions">
                <button id="run" onClick={() => {this.start_debugger()}}>Run</button>
            </div>
            <div><br />
            <textarea id="programInput" type="text" value={this.state.value} onChange={this.handleChange} />
            </div>            
        </div>
        )
    }
}

export default Debugger;