Notes About Read-Eval-Print Loops
Apparently, an (infinite) read-eval-print loop is this:
we can make it potentially not infinite by adding a state "L(oop)", that tests the values of some variables, and in some cases it goes to the state "A(bort)" instead of going back to "R(ead)".
"P(rint)" normally prints the result of the computation; in case of a "C(ompilation) E(rror)" or a "R(untime) E(rror)" we may want to print other things. So, the "P(rint)" state becomes clearer if we split it into "P"/"RE"/"CE", like this:
In most REPLs that accept multi-line commands there are two different kinds of "R(ead)": a "R(ead) F(irst line)", with a certain prompt, and a "R(ead) M(ore)", with a different prompt, for when the previous lines do not yet form a complete command. What was just a "R(ead)" in the state machines above becomes a "R(ead) F(irst line)" followed by enough "R(ead) M(ore)"s, until the input becomes complete - or until some other kind of compilation error besides "incomplete input" happens.
One way to add support for ^C or ^D to abort reading input is to treat these cases as compilation errors - as if an input that ended with ^C or ^D raised an "R(ead) E(rror)" (oops! "RE" has already been used...), that we want to treat in the "CE" state to avoid having too many arrows crossing one another in the diagram.
In the "E(val)" state, if the code is taking too long to run, the user may want to interrupt it with a ^C. We can consider that this - a "runtime abort" - is treated by the "RE" state. Also, in the "P(rint)" state, the user may want to interrupt the printout, or the stage of preparing the string to be printed. These "print abort"s may be treated inside the "P" state.
The moral of all this is that the flow of control in a read-eval-print loop with enough features is quite complex, and we have very little chance of expressing it clearly using structured code; a state machine is probably better.