Author Topic: Control flow (If, Else, ElseIf, Loop, Jump) basics  (Read 33495 times)

Pfeil

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 4647
  • RTFM
Control flow (If, Else, ElseIf, Loop, Jump) basics
« on: December 27, 2016, 12:59:58 PM »
In computer applications, whether they be programs, scripts(the distinction between which is not necessary to understand this particular subject), or in the case of VoiceAttack, commands, instructions are executed in a certain order, or "flow". To make a program more useful, we can influence or "control" this "flow" to make specific things either happen or not, when we want them to.

By default, at least in VoiceAttack(though also most programming and scripting languages) instructions, or "Actions" are executed in the order they are encountered, from the top of the list to the bottom. However, we can "tell" the computer to skip over certain actions, or even go back up the list to start again from a certain point.


Before we start with the practical application of control flow, it is important to know what evaluating a statement entails, as this is what most of these methods rely on.

The computer, despite appearances, is at its basic level a simple machine. You may have heard the expression "ones and zeroes" before, that is because those represent both possible states of any given bit of information in a(non-quantum) computer.

If a bit has a state of "0", it is said to have the value of "False".
If a bit has a state of "1", it is said to have the value of "True".

Some of you may recognize those values as those of a "Boolean" datatype, which is a representation of these basic states.

This is important, because no matter which type of statement you are presenting, be it comparing text, numbers, or Boolean values, the result of the evaluation is always either "True" or "False".


Let's start with the simplest form of influencing control flow, the "If statement":

An "If statement" is a decision between executing a block of code, or moving on directly to another part of your program/script/command.
You present a statement to be evaluated, let's use "1 = 1" as an example.
Now, we know that the number "1" does indeed equal itself, so in this case the statement evaluates to "True".
When an "If" statement is true, the computer is instructed to perform any operations tied to that statement, whereas if it were false, those operations are ignored and skipped over without even "looking" at them.

In practice, a VoiceAttack "If" statement takes the form of a "Conditional Block":
Code: [Select]
Set Text [MyText] to 'Yes'
Begin Text Compare : [MyText] Equals 'Yes'
    Write '[Blue] Action' to log
End Condition
Because it's not that useful to evaluate a statement that we know will always have the same outcome("1" will always equal "1", for example*), we must present a variable, which will contain a piece of information that can be altered by other parts of the program/script/command. In this case it is a variable named "MyText" containing a "Text" value of "Yes".

The "Begin Text Compare" action is the start of our "Conditional Block". In it, we present the statement to be evaluated; In this case we want to know if the value of our variable named "MyText" is equal to the "Text" value "Yes".
We know this statement to be true, because we have set the variable "MyText" to the value of "Yes", which we can safely assume is the same as the value we are comparing to, as it is also "Yes".
Therefore, the result of the evaluation is "True".
In VoiceAttack, when a statement is "True", any action between the "Begin" and "End Condition" actions is executed in a normal "If statement", so in this case the word "Action" will be written to the log in a blue color.

Had our statement evaluated as "False" instead, for example because we changed it to
Code: [Select]
Set Text [MyText] to 'Yes'
Begin Text Compare : [MyText] Does Not Equal 'Yes'
    Write '[Blue] Action' to log
End Condition
Which we know to be false, as the value of "MyText" is "Yes", which does equal "Yes", any actions between the "Begin" and "End Condition" actions would simply be ignored and skipped over, and not be executed.

After either executing any actions between "Begin" and "End Condition", or skipping over them, the "End Condition" block itself is encountered.
In VoiceAttack specifically, there are two options specific to this action that can greatly  influence the flow of the command: "When this block is reached, exit command if condition is met" and "When this block is reached, exit command if condition is NOT met".
These pretty much do what it says on the tin: If your statement evaluates to "True" and the former option is checked, the command will stop executing. Likewise, if your statement evaluates to "False" and the latter option is checked, the command will stop executing.
While it is possible to enable both of these options, this will result in immediate termination of the command as soon as the "End Condition" action is reached, effectively disabling any actions below it from ever executing.
If neither of these options are enabled, or the statement does not evaluate to the value of the selected option, anything below the "End Condition" action is executed.

When the end of the action list is reached, and the "Repeating" option in the "Add a Command" or "Edit a Command" window has not been enabled, the command will automatically stop executing.


Let's move on to the "If-Else statement":

In basic operation, it works the same as a normal "If statement", however using an "Else" action allows you not only to execute actions whenever a statement evaluates to "True", but also when it evaluates to "False".

As already established, actions can be ignored and skipped over entirely.
When an "Else" action is present inside a "Conditional Block" beginning with a "Begin" and ending with an "End Condition" action, any actions between the "Begin" and "Else" actions will be executed if the statement evaluates to "True", whereas any actions between the "Else" and "End Condition" action would be ignored and the command will skip to the "End Condition" action directly.
The inverse will occur if the statement evaluates to "False"; any actions between the "Begin" and "Else" actions will be skipped over to the "Else" action, and any actions between the "Else" and "End Condition" actions will be executed.

As a practical example, let's expand on our previous evaluation:
Code: [Select]
Set Text [MyText] to 'Yes'
Begin Text Compare : [MyText] Equals 'Yes'
    Write '[Blue] Action' to log
Else
    Write '[Green] Another Action' to log
End Condition
Here, when the value of "MyText" equals "Yes", the outcome is exactly the same as in our "If statement" example: The word "Action" will be written to the log in a blue color, and when this is completed, anything below the "End Condition" block will be executed.

If, however, we change our example slightly:
Code: [Select]
Set Text [MyText] to 'Yes'
Begin Text Compare : [MyText] Does Not Equal 'Yes'
    Write '[Blue] Action' to log
Else
    Write '[Green] Another Action' to log
End Condition
Because our statement now evaluates to "False", the action to write "Action" to the log in a blue color is ignored, and execution skips to the "Else" action. The "Else" action itself does not have any options, so execution immediately moves on to the action which writes "Another Action" to the log in a green color.


Now, this is where a very peculiar quirk specific to VoiceAttack comes in: When a variable does not contain a value, it has a state of "Not Set". When using a normal "If statement" the flow of the execution is not affected by this, as "Not Set" doesn't result in any statement evaluating to true other than the "Has Not Been Set" comparison.
However, when using an "Else" action, if the statement is not a "Has Not Been Set" comparison, even if the statement would logically evaluate to "True", it instead evaluates to "False".

An example of this:
Code: [Select]
Set Text [MyText] to [Not Set]
Begin Text Compare : [MyText] Does Not Equal 'Yes'
    Write '[Blue] Action' to log
Else
    Write '[Green] Another Action' to log
End Condition
You would expect this statement to evaluate to "True", because "Not Set" is not equal to "Yes", however this statement will instead evaluate to "False" because "Not Set" is a special value(more accurately it's the lack of a value, the variable contains nothing).
So this example would result in "Another Action" getting written to the log in green.

Luckily, it is possible to change this behavior to what I personally feel is more logical by enabling the "Evaluate 'Not Set' as empty (blank)" option for the "Begin" action.
With this option enabled, in our example "Action" would be written to the log in blue, because the value of "MyText" will be treated as "", and "" is not equal to "Yes", so our statement would evaluate to "True".



Next up is the "If-ElseIf statement"

You may notice that this is essentially just an "If-Else statement" with an additional "If statement" inside it; This is because "ElseIf" is merely a shorter way to describe such a construct.

This does introduce a new level of complexity: Embedded statements and evaluations.

Say for example example we want to evaluate whether a word is is equal to any of a number of values, and react differently depending on which value it is equal to.
Let us move directly into the practical example to illustrate this.

Now, if we did not have or know about "ElseIf", we may be tempted to do the following:
Code: [Select]
Set Text [MyText] to 'Yes'
Begin Text Compare : [MyText] Equals 'Yes'
    Write '[Blue] Action' to log
End Condition
Begin Text Compare : [MyText] Equals 'No'
    Write '[Green] Another Action' to log
End Condition
Begin Text Compare : [MyText] Equals 'Maybe'
    Write '[Yellow] Yet Another Action' to log
End Condition
This in itself would work, if the value of "MyText" is equal to either "Yes", "No", or "Maybe", the corresponding actions are executed and the other ignored.

So why wouldn't you just use it? Let's compare it to an "If-ElseIf statement":
Code: [Select]
Set Text [MyText] to 'Yes'
Begin Text Compare : [MyText] Equals 'Yes'
    Write '[Blue] Action' to log
Else If Text Compare : [MyText] Equals 'No'
    Write '[Green] Another Action' to log
Else If Text Compare : [MyText] Equals 'Maybe'
    Write '[Yellow] Yet Another Action' to log
End Condition
You may note that this statement is shorter than the previous example, yet it will accomplish the exact same end result.

Aside from readability, this is also more efficient for the computer. While you probably won't see a noticeable difference in performance on a modern machine, having separate "If statements" means every single one needs to be evaluated: If "MyText" has a value of "Yes", the text "Action" will be written in blue to the log, as expected, however, because they are independent "Conditional Blocks", it must also be evaluated whether the value of "MyText", which is still "Yes", equals "No" or "Maybe", even though we already know it doesn't.
In the "ElseIf" example, execution will stop as soon as the first evaluation is true, because we can already predict it will not equal any of the other options.

There is another reason, which is quite important: An "Else" action cannot be used properly with separate "If statements".
Say we insert the "Else" action into on of them, like so:
Code: [Select]
Set Text [MyText] to 'Yes'
Begin Text Compare : [MyText] Equals 'Yes'
    Write '[Blue] Action' to log
End Condition
Begin Text Compare : [MyText] Equals 'No'
    Write '[Green] Another Action' to log
Else
    Write '[Red] Final Action' to log
End Condition
Begin Text Compare : [MyText] Equals 'Maybe'
    Write '[Yellow] Yet Another Action' to log
End Condition
If we now execute the command, "Action" will be written to the log in blue, because "Yes" equals "Yes" and that evaluates to "True", yet "Final Action" will also be written to the log, because "Yes" does not equal "No" so that evaluates to "False", which executes the "Else" section.

If we do the same with our "If-ElseIf statement":
Code: [Select]
Set Text [MyText] to 'Yes'
Begin Text Compare : [MyText] Equals 'Yes'
    Write '[Blue] Action' to log
Else If Text Compare : [MyText] Equals 'No'
    Write '[Green] Another Action' to log
Else If Text Compare : [MyText] Equals 'Maybe'
    Write '[Yellow] Yet Another Action' to log
Else
    Write '[Red] Final Action' to log
End Condition
Our statements are now part of the same "Conditional Block", so not only will execution stop as soon as any statement evaluates to "True", our "Else" section will only be executed if all of our statements evaluate to "False".


Next we will look at two forms of a "Loop". These are constructs that allow you to execute a certain part of your program/script/command repeatedly, for an indefinite period of time, or, if required, a specific number of repetitions.

First up is the "While" loop:

You may notice that it is very similar to an "If statement", except that when the statement evaluates to "True", execution jumps back up after the action is complete, rather than continuing down the list. When it does this, the statement is evaluated again, and if it still evaluates to "True", it once again executes the action and jumps back up, repeating this process until the statement evaluates to "False", after which it will stop repeating and execute the next line.

Because the evaluation is repeated after the action(s) are complete, we can stop the loop by changing the value of our variable so that our statement evaluates to "False".

A few examples:
Code: [Select]
Set Text [MyText] to 'Yes'
Start Loop While : [MyText] Equals 'Yes'
    Write '[Blue] Action' to log
End Loop
This loop will keep writing "Action" in blue to the log until the value of "MyText" no longer equals "Yes".

The "Write a Value to the Event Log" action is not an integral part of the loop, however; it is merely used in this example to provide visible output of the loop working.
A loop without any actions between its start and end actions can be used to wait until a given statement evaluates to true, E.G. to wait for a keyboard key to be released, or for a variable value to change, before the lines after the "Loop End" action are executed.

It is important to note that if you create a command containing a loop like this, it will block any other commands from executing if the "Allow other commands to be executed while this one is running" option in the "Add a Command" window is not enabled. The only way to then stop the loop is to click the "Stop Commands" button on VoiceAttack's main window, or to press the "Stop All Commands Hotkey" key combination. The former can be difficult if your command is sending input to another application.
Always be careful with loops.

To stop the loop in a more controlled manner, we can change the value of "MyText" through an external command, or even using the command itself:
Code: [Select]
Begin Text Compare : [MyText] Does Not Equal 'Yes'
    Set Text [MyText] to 'Yes'
Else
    Set Text [MyText] to 'No'
End Condition - Exit when condition not met
Start Loop While : [MyText] Equals 'Yes'
    Write '[Blue] Action' to log
End Loop
Here, we're using an "If-Else statement" to change the value of "MyText".
Because our command should already have the "Allow other commands to be executed while this one is running" option enabled, it will in fact execute a second instance of the command alongside the one already running.

If the value is Anything but "Yes", it will be set to yes(Though it is important to note that our "Begin" action should have the "Evaluate 'Not Set' as empty (blank)" option enabled, as otherwise running the command the first time will always set the value to "No" because of the aforementioned quirk in VoiceAttack that "Not Set" always triggers "Else") and the loop started.
If the value is already "Yes", it will be set to "No"(though for our purposes the exact value doesn't matter, as long as it's anything but "Yes". Even "Not Set" will work), and, using the "When this block is reached, exit command if condition is NOT met" option for our "End Condition" action the new instance of our command will exit without evaluating the loop statement.
The already running instance will complete the actions within the loop, and when it returns back up to the "Start Loop While" action it will evaluate our statement, find it to be false, skip to "End Loop", and the command will exit because the end of the action list is reached.

One more useful example of a While loop is this self-terminating one:
Code: [Select]
Set small int (condition) [LoopCount] value to 10
Start Loop While : [LoopCount] Does Not Equal 0
    Write '[Blue] Action' to log
    Set small int (condition) [LoopCount] value as decremented by 1
End Loop
This loop evaluates whether "LoopCount" is "0". As "LoopCount" starts out at a value of "10", the loop will run, and when it does, the final action before re-evaluating will decrease the value by "1", a cycle it will repeat until finally "LoopCount" is decreased from "1" to "0", at which point the loop will have run completely executed ten times.

From VoiceAttack v1.6.5 onward, this structure can be substituted by the "Loop Start - Repeat a Certain Number of Times" action, which will operate in the same manner but does the counting internally.

The same example as above, using this new action:
Code: [Select]
Start Loop : Repeat 10 Times
    Write '[Blue] Action' to log
End Loop

It is worth noting that loops can be placed inside other loops, in which case the outer loop(the one that's started first) will wait indefinitely until the inner loop(s) have stopped.


Last, but certainly not least, is the "Jump", in this example also utilized to create a loop:

The "Jump" instruction is, at a very low level, what makes all of the above forms of control flow "tick". It tells the computer to skip to a specific location within the list of instructions, either down or up, and to execute from that point downward.
Let me emphasize that this does not mean the program starts running backward, just like you wouldn't be reading backward if you scrolled back up to the top of the page and started reading from there. Execution always happens from top to bottom.

To elaborate somewhat on the fact that "Jump" is what makes the other forms of control flow possible, consider even our simple if statement; Here, if our statement evaluates to "False", the "Jump" instruction(again, at a very low level) is used to skip over the instructions that should not be executed.

In VoiceAttack, "Jump" should be used only when strictly necessary; The built-in "Loop" action has additional safeguards to reduce the load on the system, and should be used whenever possible.

In the above diagram, note how the "Jump Marker" does not affect the normal execution order until the actual "Jump" action is reached. a "Jump Marker" in itself is only a visual reference as to where a "Jump" instruction will move the execution.
You can see how a while loop is created using the jump instruction, this is merely to provide a simple example. In reality, you can jump to anywhere within a command. You can also jump without the use of a marker, to go to either the beginning or the end of the action list. The latter will cause the command to stop executing, as it always does when the end of the action list is reached.




Don't worry if this stuff seems confusing at first, with practice you will become more familiar with how it all works and goes together.

I hope this attempt at an explanation clarifies, rather than confuses, but if you have questions you are of course free to ask them.


* In the digital domain, when dealing with binary computers, "1" should always equal "1", as far as I'm aware. Some mathematicians and/or philosophers may dispute any two values being equal, but for the sake of this explanation let's assume they are.


EDIT: Fixed image links

EDIT #2: Added "Loop Start - Repeat a Certain Number of Times"

EDIT #3: Attempted to clarify that a loop does not need to contain actions
« Last Edit: October 02, 2022, 10:40:39 PM by Pfeil »

Gary

  • Administrator
  • Hero Member
  • *****
  • Posts: 2800
Re: Control flow (If, Else, ElseIf, Loop, Jump) basics
« Reply #1 on: December 27, 2016, 07:00:33 PM »
Wow!  Good work on that.  Going to sticky...

Coming soon will be something akin to the 'for-next' loop (to save some steps using a, 'while' loop... will also have a definable indexer).  No ETA on that, though... however, it's higher on the list.

ralf44

  • Newbie
  • *
  • Posts: 41
Re: Control flow (If, Else, ElseIf, Loop, Jump) basics
« Reply #2 on: December 28, 2016, 02:37:41 PM »
Very thorough, thank you Pfeil  8)

sutex

  • Jr. Member
  • **
  • Posts: 91
Re: Control flow (If, Else, ElseIf, Loop, Jump) basics
« Reply #3 on: December 30, 2016, 08:50:44 PM »
Excellent ...pictures :)

Exergist

  • Global Moderator
  • Sr. Member
  • *****
  • Posts: 405
  • Ride the lightning
Re: Control flow (If, Else, ElseIf, Loop, Jump) basics
« Reply #4 on: May 23, 2017, 08:00:01 AM »
So far I haven't done much with the "small integer" (condition) data type. Does anyone have any thoughts or opinions about "best practices" or "best use cases" for employing small integers vs. integers?

Gary

  • Administrator
  • Hero Member
  • *****
  • Posts: 2800
Re: Control flow (If, Else, ElseIf, Loop, Jump) basics
« Reply #5 on: May 23, 2017, 09:06:16 AM »
At some point, I'm going to merge, 'small int (condition)' with integer.  It was left as a legacy item from the first implementation of variables (back when variables were somewhat of a novelty for the toy that VA was and not a requirement for the death star o_O lol).  As far as best practices, if you are writing things with VA that the difference between an int and a small int really matter (size), you probably shouldn't be doing that ;)  I can't even remember why I used small integers to begin with (regret).

Technomancer

  • Jr. Member
  • **
  • Posts: 98
  • I have a bad feeling about this...
Re: Control flow (If, Else, ElseIf, Loop, Jump) basics
« Reply #6 on: July 14, 2017, 11:27:04 AM »
Nice explanation and all...but I actually have a quantum-based computer, sooooo...  :P

JK!  Nice write-up!