Next Previous Contents

4. Formal development

So far we have looked only at programs that proceed from one command to another using each only once. Although this has made our first few programs easy to follow, it does not demonstrate the full potential of GLE as a programming language. In the following section we will develop the drawing routines already studied within the context of a full programming language. We shall look at how, by reusing code and defining subroutines, we can greatly simplify the construction of a complex program. These concept will be familiar territory to anyone with a knowledge of other high level programming languages such as C or Pascal.

To begin with we formalize some of the syntax we have been using. We define a command as a keyword that prepares GLE for a task. Following this is a set of qualifiers that define the parameters of the task; the width and height of a box or the coordinates of a point for example. We also distinguish between qualifiers that are single variables and those that are commands in their own right. The instruction set to draw a box may be written as:

box 2 4 fill grey20 justify CR
We see that 2 and 4 are single variables that are required to draw the box successfully. The other qualifiers take the form of a sub command followed by another qualifier, these are optional - removing them will result in the box being drawn with the default settings. The order of the sub-commands does not matter but we cannot separate command and qualifier, nor can we place the sub-commands before the single variables, the 2 4 must not be separate from the box command.

When GLE finds a keyword in the script it looks to the right of that word for any qualifiers that are necessary, then to the right of those for any optional sub-commands and their qualifiers. GLE ignores all multiple spaces, though each word must, at least, be separated by a single space. The end of line character is a signal to GLE that the current command sequence has ended and it should proceed to the next line of code.

The set command is a keyword and, if we have several variables to specify at the same time, then we may shorten the usage to a single line. Suppose we have the code:

set hei 3
set color red
set just center
set font ss  
GLE reads the keyword set; sets the font height to 3, then forgets about the set command. In the second line it follows the same process but sets the colour to red. It makes sense that we can shorten the code to a single set command that we write as,
set hei 3 color red just center font ss
Which accomplishes the same thing but using the command to sub-command system.

4.1 Variables and expressions

So far we have explicitly provided values for each command we have used. For example, when drawing a box we gave numerical values for both the height and width. It is useful, however, to be able to define a variable that represents a number or a string of text. For example we may write:

a=3.1415
b=3
box a b
Which tells GLE to draw a box of width ‘a’ and height ‘b’, where ‘a’ and ‘b’ are defined to have the numerical vales 4 and 3.1415. If a variable is not defined before it is used in the program then it will take the default value of zero.

Any combination of letters and numbers may be used to represent a numerical variable, it makes sense to choose ones that are both memorable and sensible. Text strings can be represented by variables that end in a ‘$’ sign, text$ for example. This works fine for most commands but the text command treats text strings as plain text. For example, the set of commands:

a$=Hello
text a$
Will write the text ‘a$’ to the screen. To display the variable text we use the command write, the only difference from text being the ability to detect and display text strings.
text1$=color
text2$=red
set color text2$
write This is the text1$ text2$ text3$
The string variable ‘text3$’ is undefined at this point in the program. On fininding this GLE will simple disply the plain text ‘text3$’ . The write command does not preclude the use of $ signs, just any defined string variable containing them.

GLE contains and extensive library of built in functions which may also be used to simplify a program. Many of the expressions are mathematical, including all the standard operations: ‘+’ for add, ‘-’ for subtract, ‘*’ and ‘/’ for multiply and divide. We may include Boolean operators (AND, OR) and relational operators (<, >, =>, <=, =, <>). There are also trigometric and exponential functions, a set of functions for manipulating strings of text, and functions that return the current date and time. A full listing of the available expressions is given in the appendix.

Wherever a string or numerical value is used, an expression can also be used. Equally, numerical and string variables can be substituted into an expression. The following lines of code will draw three identical lines:

rline 3 2 

rline 9/3 sqrt(4)

a=9
b=3
c=1+1
rline a/b sqrt(2*c)

Expressions in GLE are delimited by white space and therefore we must not include any spaces within the expressions themselves. Thus, rline 3*2 4 is a valid expression, whilst rline 3 * 2 4 is not.

4.2 Conditional logic and programming loops

An important part of GLE programming is the use of programming loops and the logic that is needed to execute them. Suppose we have a repetitive task whose execution differs slightly each time. Instead of writing out each individual step we use a loop, a repeated piece of code that runs until the conditions specified at the top of the loop are satisfied.

The circles

We look at a simple example of the use of a loop


circles.gle
size 20 20
amove 10 10
for x = 0.01 to 100 step 0.01
        circle 1/x
        next x  

Draws a series of concentric circles aproaching a limit at the center of the page. The loop is determined by the for command and the next x command, everything between these is part of the loop. The three qualifiers that follow this command determine the number and spacing of the loops, consider the loop,
for x = a to b step c
        (....)
        next x
On reaching this line of code GLE sets x equal to a and then repeats the following procedure,

There is nothing special about x, it can be any variable we wish. Notice, however, that the variable used in a loop is not a local variable as with a subroutine. If we define x in the code preceding the loop then in the following code the value of x will be the last value it took in the loop.

If...then statements

A similar command can be used to execute different code depending on whether a given statement is true or not.

if xpos()=3 then
        text We are at x=3
else
        text We are not at x=3
end if
If the statement immediately following the if command is true then GLE executes the code between the then command and the else command, if it is false then GLE executes the code between else and end if. Either way GLE then moves onto the line of code imediatly following end if. In this case the program will print ‘We are at x=3’ if the x coordinate is 3, or ‘We are not at x=3’ if it is not.

The superellipse

We look at a more concrete example using both the loop and conditional logic, this illustrates the great advantage GLE has over traditional drawing programs. The following code draws Piet Hein's superellipse, a set of shapes that satify, for different values of n, the equation (x/a)n + (y/b)n = 1 For a discussion of the superellipses origin, and the diagram itself, see J. Phys: Condensed Matter 13, 2271 (2001).


!Superellipse.gle - Graphical representation of Piet Hein's superellipse

size 24 18 
 
amove 10 9 
begin origin            !Sets the origin of the ellipse at (10,9) 
 
a=5                     !a and b represent the excentricity of the ellipse
b=8 
n=1.5 

amove -a -b             !Draws the box that represents the limit as n tends to 
box 2*a 2*b             !infinity
amove 0 0 
 
for j=0.5 to 10 step 0.25 
n=j                     !n is the exponent of the ellipse
 
for i=0 to 360 step 1   !Calculates the radial distance from the x and y
ang=i*3.14/180 
c=cos(ang) 
if (c<0) then 
  c=-c 
end if 
s=sin(ang) 
if (s<0) then 
  s=-s 
end if 
ax=(c/a)^n              !ax is the projection of the radial coordinate along
                        !the x axis
ay=(s/b)^n              !ay is the same along the y axis
zob=1/(ax+ay) 
if (zob<0) then 
  zob=-zob 
end if 
zub=zob^(1/n) 
x=zub*cos(ang) 
y=zub*sin(ang) 
if i=0 then 
 amove x y 
else 
 aline x y 
end if 
 
next i 
next j  

4.3 Subroutines

Although variables may be defined anywhere within a code it makes sense to place them at or near the start of the program. This allows the variables of the program to easily be changed if the need arises, and also, reduces the number of lines within the central body of a program. Reducing the size of the main programming loop makes the program easier to understand, and to check for errors. A similar technique applies to the writting of subroutines. A subroutine is a distinct section of code that perform a particular task. For example, within a program we may be required to draw a triangle,

amove 2 2
rline 2 0
rline -1 sqrt(3)
rline -1 -sqrt(3)
We can write this section of code at the beginning of the script using the sub and end sub commands.
sub triangle
        amove 2 2
        rline 2 0
        rline -1 sqrt(3)
        rline -1 -sqrt(3)
end sub
Indentation is optional but can make the program easier to understand. We may call the triangle at any point within the program body using the command,
@triangle
Where the @triangle commmand tells GLE to execute a subroutine with the name ‘triangle’ .

This has accomplished our goal of simplifying the program body by removing self contained code to the start of the program and giving it a logical call name. It would, however, be far more useful if we could specify the size and position of the triangle each time we draw it. We do this by specifying a series of local variables within the subroutine.

sub triangle x y size
        amove x y
        rline (size) 0
        rline -(0.5)*(size) (0.5)*(sqrt(3))*(size)
        rline -(0.5)*(size) -(0.5)*(sqrt(3))*(size)
end sub
Again we may call the triangle at any point in the program, however we must now specify the size and position of the triangle.
@triangle 2 3 1
@triangle 0.2 sqrt(3.4) 0.5
@triangle a b c-4

The variables used within a subroutine are entirly separate from the program body. They are forgotten as soon as the subroutine is finished; so the size variable in the above line of code is undefined in the program body. We are, of course, free to define a variable ‘size’ and use it within the program body. It will neither affect nor be affected by the execution of the subroutine.

Commonly used subroutines can be removed entirly from the programming script and placed in a separate file which can be made available to any number of GLE programs. The following subroutine substitutes a character (a snowflake) from one of the font tables as a marker for either a graph or diagram.


!subsnow.gle - Subroutine that prints a snowflake
sub subsnow size                !Size is the font size of the snowflake
gsave                           !Saves the initial graphic state
set font pszd hei size
t$ = "\char{102}"
rmove -twidth(t$)/2 -theight(t$)/2  !Centers the snowflake over the initial coordinates
write t$
grestore                        !Restores the saved graphic state
end sub  

There are several new elements within this code. The gsave and grestore commands respectively save and restore the current graphic state. When the subroutine is called GLE remembers the initial coordinates and settings of the font height and type. Thus the set command that appears in the subroutine is then not allowed to affect the main program loop, an advantage if we wish to construct a piece of code that will fit into any program we choose.

The rest of the subroutine writes character 102 (a snowflake) from the postscript zapdingbat font at the center of the initial coordinates. The quotation marks around the definition of ‘t$’ indicate to GLE that the string reference is to be taken as a special character and not a literal piece of text. The twidth(t$) and theight(t$) are special functions that give the width and height of the character in brackets, if it was printed with the current font size settings.

This piece of code is a perfectly valid subroutine and can be placed at the top of any GLE program. However, if we draw a lot of snowflakes we can reduce the complexity of our GLE script by calling the subroutine from within the program. The above code is placed in a separate file snowflake.gle. Notice that this is not a valid GLE file, it does not have a size declaration nor any program body. We can however tell GLE that this subroutine is to be compilied with a program using the include command. We write,


!Snowflake - produces a boxed snowflake
size 5 5
include subsnow.gle             !Subroutine for drawing a snowflake
define marker snow subsnow      !Defines a marker to be the snowflake produced 
                                !by the subroutine
amove 2.5 2.5                   
box 2 2 just CC                 
marker snow 1                   !Draws a 1cm snowflake

The code defines a new marker snow that draws the snowflake in the subroutine in the same way as any other marker. The box is to show that the snowflake is centered on the initial coordinates.

We have looked at including subroutines in our program, but we can also use the include command to run any GLE script at any point within another program. Sometimes we may have a very large or complex program that we wish to use again in another script, in this case the include command can be used instead of copying out the entire file. If we wish to call a very complex program then we can also use the command bigfile, this is identical to the include command except the program will be read and compliled one line at a time. This can speed up the execution of complex programs, however there are certain restrictions: some complex multi-line commands will not work and we cannot define subroutines within the bigfile program.

Frequently used subroutines can be stored in a separate user directory and called by any program. For example the following code will produce a shadowing effect on any given text.


sub ziptext tt$
gsave
for i = 1 to 0 step -0.05
set color (i)
write tt$
rmove -0.05 0.025
next i
set color white
write tt$
grestore
end sub

We call the subroutine at any point using the usual set of commands, we are able to pass any text string we wish onto the subroutine.

!glezip.gle - Demonstrates the ziptext command
size 7 5 
include ziptext.gle

amove 2 1
set font rm hei 2 
@ziptext "GLE"

We also look at a final example of the use of subroutines which gives an idea of the maximum processing speed that GLE is capable of. When using programming loops it is a good idea to start with only a small number of iterations, before building up to the number required to produce an accurate looking diagram. It is also important to remember that most postscript viewers are not designed to display very complex diagrams and will not do so with any haste.


!fractal.gle - Draws a fractal
size 18 20

rx=1            !Defines the position of the coloured markers
ry=1
bx=9
by=16
gx=17
gy=1

amove rx ry     !Draws the coloured markers
set color red
marker dot 0.4 
amove bx by 
set color blue
marker dot 0.4 
amove gx gy
set color green
marker dot 0.4 

                !This set of subroutines moves to halfway between the initial
                !coordinate and the coloured marker and draws a dot
sub green               
set color green
amove (gx+xpos())/2 (gy+ypos())/2
end sub

sub blue
set color blue
amove (bx+xpos())/2 (by+ypos())/2
end sub

sub red
set color red
amove (rx+xpos())/2 (ry+ypos())/2
end sub

amove 5 5 

for n=1 to 100000 step 1        !Number of iterations defines the speed at
                                !which the program compiles

        r=rnd(1)                !Executes each of the subroutines randomly with
        if r<0.333 then              !the same probability
                @blue
                else
                if r<0.6666 then
                        @green
                        else
                        @red
                end if
        end if

if n>15 then            !Ignores the first few dots which are influenced by
marker dot 0.05         !initial start position
next n
else 
next n


Next Previous Contents