Control Flow
Control Flow is the part of a language that is concerned with determining how the program behaves beyond just a series of instructions; control flow tells the computer what to do when, giving it an instruction order. Control flow is almost always necessary for a useful (or could-be-but-isn't-useful) language, but can be difficult to do esoterically. This article serves as a documentation of different kinds of control flow.
Primitives
At the very lowest level, assembly and machine code, control flow is severely limited. There is unconditional control flow- JUMP or BRANCH- which changes the current line of the program (JUMP 5
, for example, would go to the fifth line in the program (though usually an ASM would use labels instead)). JUMP behaves like GOTO, mostly because it is goto.
Conditional Jumps are invariably available as well, though their condition is severely limited. They usually target a single registry and jump only if its value falls in a certain group- usually ZERO (JZ), NONZERO (JNZ), NEGATIVE (JN), POSITIVE (JP), NONNEGATIVE (JNN), or NONPOSITIVE (JNP). They do exactly what is expected- jump, but if and only if a certain condition is met.
In addition, a few assemblies have singular conditional instructions- usually for set. These are the same as conditional jumps, including with the same groups for conditions, but instead SET a register to a certain value if and only if their condition is met in the target register.
Some JUMPs and GOTOs have "labels" supported, as mentioned above. Normally in an instruction, particularly in the Machine Code, a JUMP will go to a line by referencing it by number. This strategy becomes cumbersome, particularly because line number changes if you insert a line before the target. To circumvent this, many languages (though only ones supporting JUMPs and GOTOs) allow labels to be used- meaningless names marking lines that are converted to line number at compile time. This makes it significantly easier for the programmer to target specific lines by handling it all internally instead of forcing them to set lines by hand, AND allows for a nicer appearance.
Computed Goto
A computed goto goes to a line by calculating a line number first, then by going to that line. These are seldom used, as going to a line of which the index can only be found at runtime can be very difficult to think about; when these are used, it's often in the form of choosing from a set of likely values, such as entering a subroutine which is selected at runtime.
Alternatives
Alternatively to using JUMP/GOTO as a control flow primitive, a select few esoteric languages use COME FROM as their control flow. COME FROM, by analogy to GO TO, starts executing from the location of the COME FROM the moment that line is reached. This leads to problems with multiple COME FROMs, but it's nothing a little concurrency bullshit can't fix.
COME FROMs may also support labels (though it's less fun that way) and, *shiver*, computed COME FROMs.
GOTO <label> WHEN <condition>
is also an option for event-driven assembly, which registers an event handler that goes to the line marked by <label> the moment the condition supplied is met, even if it's no longer on that line.
Higher-level Things
Most languages of ease support constructs other than a goto/label pair, because goto is evil. These tend to have "code blocks" associated, which are groups of instructions all executed together based on the behavior of the structure and its conditions.
If-Then
construct
Evaluates its code if and only if its condition evaluates to true. Does not repeat.
Else
A block that is evaluated if its condition evaluates to false
Else-If
Used for towering if-then-elses.
While loop
A while loop is a loop that accepts a single condition then executes its code if it is true, then tests its condition again and runs its code if it is true, until either the end of time or the condition is false. When the condition is false upon checking, the while loop exits and continues in the code.
Do..While loop
A do..while loop is a while loop guaranteed to execute at least once: it executes its code, then returns to the top if its condition evaluates to true. Essentially, the condition is checked at the end instead of the beginning.
Until Loop
Behaves exactly like a while loop with its condition negated. Repeats its code until its condition evaluates to true upon checking. do..until is also available, naturally.
For loop
A for loop accepts three things, separated by semicolons (all of them optional, but the semicolons must be included): A variable declaration, a condition, and a variable updater. When it starts, it creates the variable at the defined value. It then executes its code, with that variable available to the instructions, and then returns to the top. It updates the variable with the updater (usually var++
or var--
) and then checks if it still meets the criterium. If it does, it executes the code again with the new variable, if it does not, it exits and deletes the variable.
Foreach loop
The foreach loop accepts a variable name and an iterable. It loops for the length of the iterable, setting the variable to each item of it in order. The variable is available to code used in the block.
Break and Continue
BREAK exits the current construct or exits to the top level of the constructs. CONTINUE stops the current iteration of a loop, and immediately jumps to the next iteration.
Case/Switch Construct
The case construct is a "switch <var>", followed by a list of cases mapped to code (and, oftentimes, a break, depending on the language.) It iterates through the cases and starts executing at the first one that it encounters as "true". If there is a break, it only executes that case.
Redo statement
A redo statement is a while loop whose condition is always truthy, thus looping forever. In some languages it instead causes execution to immediately jump to the top of a while loop (whether or not the loop condition is met).
Retry statement
Retry is a command used in error handling that attempts running the code that threw an error again.
Procedures
A procedure is a now-seldom-used language construct that accepts a list of arguments then executes code when called. It is like a function without a return value.
Subroutines
Also known as functions, subroutines accept a list of arguments that they call code with, then return a value that can be used in an expression.
Coroutines
Coroutines allow multiple entry points. Coroutines capture the state on exit and when re-entered continue from the saved state. Not all languages support coroutines and in general coroutines are not used very often and state machines are used instead where applicable.
Error handling
Try ... catch ... finally
Tries to execute a series of statements and if an error is thrown that shall be caught control flow is redirected to the corresponding catch block. In any case the finally block is always executed (usually a return in a finally will overwrite any returns within the try and/or catch blocks). Some versions (Python) can also employ an else block, which runs only if the try block finished with no errors.
On Error Resume Next
Most famously known from Visual Basic this construct continues with the next statement when an error occurred.
On Error Goto
Specifies a target label to jump to in case of an error. If an error occurs control flow resumes at the specified location.
Esoteric Things
Do loop until failure
Do loop until failure is a type of loop with no condition- or more accurately, the condition is the success of the body. Essentially, it continues executing the body of the code until it fails, at which point it ignores the error and the loop exits.
For..else, while..else
This one is actually used in some languages (e.g. Python): An else block on a loop, as opposed to an if statement. It executes if the loop does not break.
β-reduction
The lambda calculus uses the power of beta reduction to figure out what code to run. Expressions in the lambda calculus can contain subexpressions that generally need to be evaluated, but those subexpressions can be simplified out of the expression (depending on the circumstances) which means they won't ever be evaluated. While loops can be constructed by creating expressions that contain themselves as subexpressions, and if-else statements can be emulated by an expression that evaluates to one of two options depending on another expression. The program avoids picking the wrong branch of an if-else construct by simplifying the wrong branch out of the expression first.
Continuation passing style
Continuations are a piece of data that represents the rest of the code to run next, such as a list of instructions, or a function to run after the current one returns. A function can choose to return a specific continuation in the same way it would decide how to return any other piece of data, and the program will execute that code next. Programmers rewrite programs in many languages into 'continuation passing style', which is where all functions take continuations as arguments and run or return them as the sole way to decide what code to run next.