Loops
Loops provide a way to repeat certain operations multiple times. Consider this function:
MODULE MAIN
# THIS FUNC PRINTS: 1 2 3 4 5 6 7 8 9 10 DONE
FUNC START()
CONSOLE::INIT()
CONSOLE::PRINT_INT(1)
CONSOLE::PRINT(" ")
CONSOLE::PRINT_INT(2)
CONSOLE::PRINT(" ")
CONSOLE::PRINT_INT(3)
CONSOLE::PRINT(" ")
CONSOLE::PRINT_INT(4)
CONSOLE::PRINT(" ")
CONSOLE::PRINT_INT(5)
CONSOLE::PRINT(" ")
CONSOLE::PRINT_INT(6)
CONSOLE::PRINT(" ")
CONSOLE::PRINT_INT(7)
CONSOLE::PRINT(" ")
CONSOLE::PRINT_INT(8)
CONSOLE::PRINT(" ")
CONSOLE::PRINT_INT(9)
CONSOLE::PRINT(" ")
CONSOLE::PRINT_INT(10)
CONSOLE::PRINT(" ")
CONSOLE::PRINT("DONE")
END FUNC
END MODULE
Instead of duplicating the code multiple times, we can make a variable I which counts upwards.
MODULE MAIN
# THIS FUNC PRINTS: 1 2 3 4 5 6 7 8 9 10 11 12...
FUNC START()
VAR I: INT
0 -> I
CONSOLE::INIT()
LOOP
I + 1 -> I
CONSOLE::PRINT_INT(I)
CONSOLE::PRINT(" ")
END LOOP
END FUNC
END MODULE
The code between LOOP and END LOOP will repeat endlessly. Each time through this loop, I + 1 -> I will increase the value of I by one. This prints the numbers 1 through 10, but then it keeps counting forever. How to end the loop?
DROPโ
The DROP statement is used to exit a LOOP. (DROP is often called break in other programming languages.) It moves control to the next statement after END LOOP. Here is how to use DROP to stop counting when we reach 10:
MODULE MAIN
# THIS FUNC PRINTS: 1 2 3 4 5 6 7 8 9 10 DONE
FUNC START()
VAR I: INT
0 -> I
CONSOLE::INIT()
LOOP
I + 1 -> I
# STOP WHEN WE REACH 10
IF I > 10 THEN
DROP
END IF
CONSOLE::PRINT_INT(I)
CONSOLE::PRINT(" ")
END LOOP
CONSOLE::PRINT("DONE")
END FUNC
END MODULE
LIFTโ
The LIFT statement causes control to move immediately to the top of the loop. (LIFT is often called continue in other programming languages.) In other words, it skips the rest of the loop. We can use LIFT to skip the even numbers:
MODULE MAIN
# THIS FUNC PRINTS: 1 3 5 7 9 DONE
FUNC START()
VAR I: INT
0 -> I
CONSOLE::INIT()
LOOP
I + 1 -> I
# SKIP THE EVEN NUMBERS
IF I % 2 = 0 THEN
LIFT
END IF
# STOP WHEN WE REACH 10
IF I > 10 THEN
DROP
END IF
CONSOLE::PRINT_INT(I)
CONSOLE::PRINT(" ")
END LOOP
CONSOLE::PRINT("DONE")
END FUNC
END MODULE
Relationship to JUMPโ
If you look at the Chombit assembly language that the compiler produces, you will see that LOOP is implemented using JUMP instructions. Normally programs go step by step, executing one statement after another. JUMP causes the CPU to move to an arbitrary step, instead of the next one.
We can understand LOOP in terms of jumps:
. . .
LOOP
# ๐ฏ POSITION A
I + 1 -> I
IF I % 2 = 0 THEN
# ๐ JUMP UP TO POSITION A
LIFT
END IF
# STOP WHEN WE REACH 10
IF I > 10 THEN
# ๐ JUMP DOWN TO POSITION B
DROP
END IF
CONSOLE::PRINT_INT(I)
CONSOLE::PRINT(" ")
# ๐ JUMP UP TO POSITION A
END LOOP
# ๐ฏ POSITION B
CONSOLE::PRINT("DONE")
. . .
Some programming languages allow arbitrary jumps, for example using goto statement. This is much more flexible, however it turns out that jumps are very difficult to understand and debug. Code is much easier to understand if we restrict ourselves to use only LOOP, LIFT, DROP, and IF for jumping around. In practice, this restriction does not limit the kinds of programs that we can write.
Nesting loopsโ
You can put LOOP inside another LOOP. For example, suppose a chess board has 8 rows and 8 columns. To clear every square, we might use an outer LOOP to count through the rows, and an inner LOOP to count through the columns.
Here is a simple example of two loops:
MODULE MAIN
# THIS FUNC WILL PRINT:
#
# 1 2 3 4 5
# 1 2 3 4
# 1 2 3
# 1 2
# 1
FUNC START()
VAR I: INT, TOP: INT
CONSOLE::INIT()
5 -> TOP
LOOP
# ๐ฏ POSITION C
1 -> I
LOOP
# ๐ฏ POSITION D
CONSOLE::PRINT_INT(I)
CONSOLE::PRINT(" ")
I + 1 -> I
IF I > TOP
DO DROP # ๐ JUMP DOWN TO POSITION E
# ๐ JUMP UP TO POSITION D
END LOOP
# ๐ฏ POSITION E
CONSOLE::PRINT("{N}") # NEW LINE
TOP - 1 -> TOP
IF TOP <= 0
DO DROP # ๐ JUMP DOWN TO POSITION F
# ๐ JUMP UP TO POSITION C
END LOOP
# ๐ฏ POSITION F
END FUNC
END MODULE
Numbered loopsโ
What if we wanted to DROP out of two nested loops? For example, suppose we're trying to find an empty square on a chessboard. An outer LOOP counts through the rows of the board, and the inner LOOP counts through the columns. When we find an empty cell, then we exit both loops.
To support this, Hybrix allows you to label your LOOP with numbers, and then DROP and LIFT can refer to those numbers. In the example below, the outer loop is number 100 and the inner loop is number 200. This allows DROP 100 to exit both loops:
MODULE MAIN
# RETURNS THE CHESS PIECE AT THE GIVEN ROW AND COLUMN
# OR 0 IF THE SQUARE IS EMPTY.
FUNC GET_CELL(COL: INT, ROW: INT): INT
# TODO: PUT SOME CODE HERE
END FUNC
FUNC START()
VAR ROW: INT, COL: INT
# FIND THE FIRST EMPTY SQUARE
0 -> ROW
LOOP 100
# ๐ฏ POSITION G
0 -> COL
LOOP 200
# ๐ฏ POSITION H
IF MAIN::GET_CELL(COL, ROW) = 0
DO DROP 100 # ๐ JUMP DOWN TO POSITION J
COL + 1 -> COL
IF COL >= 8
DO DROP 200 # ๐ JUMP DOWN TO POSITION I
# ๐ JUMP UP TO POSITION H
END LOOP 200
# ๐ฏ POSITION I
ROW + 1 -> ROW
IF ROW >= 8
DO KERNEL::FAIL("BOARD IS FULL")
# ๐ JUMP UP TO POSITION G
END LOOP 100
# ๐ฏ POSITION J
# TODO: PUT A CHESS PIECE AT (COL, ROW)
END FUNC
END MODULE
Note:
LOOP 100does NOT mean loop 100 times. The number 100 is just a label for the loop. Its numeric value is unimportant.
The number label for LOOP is only required if a LIFT statement refers to it. And the number label for END LOOP is only required if a DROP statement refers to it. Thus, the above example can be written more concisely as:
MODULE MAIN
# RETURNS THE CHESS PIECE AT THE GIVEN ROW AND COLUMN
# OR 0 IF THE SQUARE IS EMPTY.
FUNC GET_CELL(COL: INT, ROW: INT): INT
# TODO: PUT SOME CODE HERE
END FUNC
FUNC START()
VAR ROW: INT, COL: INT
# FIND THE FIRST EMPTY SQUARE
0 -> ROW
LOOP
0 -> COL
LOOP
IF MAIN::GET_CELL(COL, ROW) = 0
DO DROP 100
COL + 1 -> COL
IF COL >= 8
DO DROP
END LOOP
ROW + 1 -> ROW
IF ROW >= 8
DO KERNEL::FAIL("BOARD IS FULL")
END LOOP 100
# TODO: PUT A CHESS PIECE AT (COL, ROW)
END FUNC
END MODULE