############################################################################ # # File: dijkstra.icn # # Subject: Procedures for Dijkstra's "Discipline" control structures # # Author: Frank J. Lhota # # Date: December 9, 2003 # ############################################################################ # # This file is in the public domain. # ############################################################################ # # The procedures do_od and if_fi implement the "do ... od" and "if ... fi" # control structures used in the book "A Discipline of Programming" by # Edsger W. Dijkstra. This book uses a programming language designed to # delay implementation details, such as the order in which tests are # performed. # # Dijkstra's programming language uses two non-ASCII characters, a box and # a right arrow. In the following discussion, the box and right arrow # characters are represented as "[]" and "->" respectively. # # The "if ... fi" control structure is similar to multi-branch "if" statements # found in many languages, including the Bourne shell (i.e. the # "if / elif / fi" construct). The major difference is that in Dijkstra's # notation, there is no specified order in which the "if / elif" tests are # performed. The "if ... fi" structure has the form # # if # Guard1 -> List1 # [] Guard2 -> List2 # [] Guard3 -> List3 # ... # [] GuardN -> ListN # fi # # where # # Guard1, Guard2, Guard3 ... GuardN are boolean expressions, and # List1, List2, List3 ... ListN are lists of statements. # # When this "if ... fi" statement is performed, the guard expressions are # evaluated, in some order not specified by the language, until one of the # guard expressions evaluates to true. Once a true guard is found, the list # of statements following the guard is evaluated. It is a fatal error # for none of the guards in an "if ... fi" statement to be true. # # The "do ... od" control is a "while" loop structure, but with multiple # loop conditions, in style similar to "if ... fi". The form of a Dijkstra # "do" statement is # # do # Guard1 -> List1 # [] Guard2 -> List2 # [] Guard3 -> List3 # ... # [] GuardN -> ListN # od # # where # # Guard1, Guard2, Guard3 ... GuardN are boolean expressions, and # List1, List2, List3 ... ListN are lists of statements. # # To perform this "do ... od" statement, the guard expressions are # evaluated, in some order not specified by the language, until either a # guard evaluates to true, or all guards have been evaluated as false. # # - If all the guards are false, we exit the loop. # - If a guard evaluates to true, then the list of statements following this # guard is performed, and then we loop back to perform this "do ... od" # statement again. # # The procedures if_fi{} and do_od{} implement Dijkstra's "if ... fi" and # "do ... od" control structures respectively. In keeping with Icon # conventions, the guard expressions are arbitrary Icon expressions. A guard # is considered to be true precisely when it succeeds. Similarly, a statement # list can be represented by a single Icon expression. The Icon call # # if_fi{ # Guard1, List1, # Guard2, List2, # ... # GuardN, ListN # } # # suspends with each result produced by the expression following the true # guard. If none of the guards succeed, runerr() is called with an appropriate # message. # # Similarly, the Icon call # # do_od{ # Guard1, List1, # Guard2, List2, # ... # GuardN, ListN # } # # parallels the "do ... od" statement. As long as at least one guard # succeeds, another iteration is performed. When all guards fail, we exit # the loop and do_od fails. # # The test section of this file includes a guarded command implementation of # Euclid's algorithm for calculating the greatest common denominator. Unlike # most implementations of Euclid's algorithm, this version handles its # parameters in a completely symmetrical fashion. # ############################################################################ # # Links: none # ############################################################################ ############################################################################ # # Produces a set of the indices of all the guard expressions in exp. # ############################################################################ procedure __Dijkstra_guard_index_set(exp) local result result := set() every insert(result, 1 to *exp by 2) return result end # __Dijkstra_guard_index_set ############################################################################ procedure do_od(exp) #: Dijkstra's do_od construct local all_guards, curr_guard all_guards := __Dijkstra_guard_index_set(exp) # Remember to use refreshed co-expressions so that they can be evaluated # more than once! while @^exp[ curr_guard := !all_guards ] do @^exp[ curr_guard + 1 ] end # do_od ############################################################################ procedure if_fi(exp) #: Dijkstra's if_fi construct local all_guards, curr_guard all_guards := __Dijkstra_guard_index_set(exp) if @exp[ curr_guard := !all_guards ] then suspend | @exp[ curr_guard + 1 ] else runerr(500, "if_fi: no guards succeeded") end # if_fi $ifdef TEST ############################################################################ # # Dijkstra version of the familiar Euclidean algorithm for gcd. # ############################################################################ procedure gcd(x, y) # Use static variables so that co-expressions can share them static lx, ly lx := abs(x) ly := abs(y) do_od{ lx >= ly > 0, lx %:= ly, ly >= lx > 0, ly %:= lx } return if_fi{ lx = 0, ly, ly = 0, lx } end # gcd procedure main(arg) local a, b a := integer(arg[1]) | 1836311903 b := integer(arg[2]) | 1134903170 return write("gcd(", a, ",", b,")=",gcd(a, b)) end # main $endif