@database beginner.guide 

@Master beginner 

@Width 75 


This is the AmigaGuideŽ file beginner.guide, produced by Makeinfo-1.55 from 
the input file beginner.


@NODE "main" "Introduction to the Examples"
@Next "String Handling and I-O" 
@Prev "OOE.guide/main" 
@Toc "Contents.guide/main" 

Introduction to the Examples
****************************

   In this part we shall go through some slightly larger examples than
those in the previous parts.  However, none of them are too big, so they
should still be easy to understand.  The note-worthy parts of each example
are described, and you may even find the odd comment in the code.  Large,
complicated programs benefit hugely from the odd well-placed and
descriptive comment.  This fact can't be stressed enough.

   All the examples will run on a standard Amiga, except for the one which
uses @{b }ReadArgs@{ub } (an AmigaDOS 2.0 function).  It is really worth upgrading
your system to AmigaDOS 2.0 (or above) if you are still using previous
versions.  The @{b }ReadArgs@{ub } example can only hint at the power and
friendliness of the newer system functions.  If you are fortunate enough
to have an A4000 or an accelerated machine, then the timing example will
give better (i.e., quicker) results.

   Supplied with this Guide should be a directory of sources of most of
the examples.  Here's a complete catalogue:

@{b }simple.e@{ub } 
     The simple program from the introduction.  See @{"A Simple Program" Link "Introduction.guide/A Simple Program" }.

@{b }while.e@{ub } 
     The slightly complicated @{b }WHILE@{ub } loop.  See @{"WHILE loop" Link "Introduction.guide/WHILE loop" }.

@{b }address.e@{ub } 
     The program which prints the addresses of some variables.  See
     @{"Finding addresses (making pointers)" Link "Types.guide/Finding addresses (making pointers)" }.

@{b }static.e@{ub } 
     The static data problem.  See @{"Static data" Link "Types.guide/Static data" }.

@{b }static2.e@{ub } 
     The first solution to the static data problem.  See @{"Static data" Link "Types.guide/Static data" }.

@{b }except.e@{ub } 
     An exception handler example.  See @{"Raising an Exception" Link "Exceptions.guide/Raising an Exception" }.

@{b }except2.e@{ub } 
     Another exception handler example.  See @{"Raising an Exception" Link "Exceptions.guide/Raising an Exception" }.

@{b }static3.e@{ub } 
     The second solution to the static data problem, using @{b }NEW@{ub }.  See
     @{"List and typed list allocation" Link "Memory.guide/List and typed list allocation" }.

@{b }float.e@{ub } 
     The floating-point example program.  See @{"Floating-Point Functions" Link "FloatingPoint.guide/Floating-Point Functions" }.

@{b }bintree.e@{ub } 
     The binary tree example.  See @{"Binary Trees" Link "Recursion.guide/Binary Trees" }.

@{b }tree.e@{ub } 
     The @{b }tree@{ub } and @{b }integer_tree@{ub } classes, as a module.  See @{"Inheritance in E" Link "OOE.guide/Inheritance in E" }.

@{b }tree-use.e@{ub } 
     A program to use the @{b }integer_tree@{ub } class.  See @{"Inheritance in E" Link "OOE.guide/Inheritance in E" }.

@{b }set.e@{ub } 
     The simple, inefficient @{b }set@{ub } class, as a module.  See @{"Data-Hiding in E" Link "OOE.guide/Data-Hiding in E" }.

@{b }set-use.e@{ub } 
     A program to use the @{b }set@{ub } class.  See @{"Data-Hiding in E" Link "OOE.guide/Data-Hiding in E" }.

@{b }csv-estr.e@{ub } 
     The CSV reading program using E-strings.  See @{"String Handling and I-O" Link "String Handling and I-O" }.

@{b }csv-norm.e@{ub } 
     The CSV reading program using normal strings.  See
     @{"String Handling and I-O" Link "String Handling and I-O" }.

@{b }csv-buff.e@{ub } 
     The CSV reading program using normal strings and a large buffer.  See
     @{"String Handling and I-O" Link "String Handling and I-O" }.

@{b }csv.e@{ub } 
     The CSV reading program using normal strings, a large buffer, and an
     exception handler.  See @{"String Handling and I-O" Link "String Handling and I-O" }.

@{b }timing.e@{ub } 
     The timing example.  See @{"Timing Expressions" Link "Timing Expressions" }.

@{b }args.e@{ub } 
     The argument parsing example for any AmigaDOS.  See @{"Any AmigaDOS" Link "Any AmigaDOS" }.

@{b }args20.e@{ub } 
     The argument parsing example for any AmigaDOS 2.0 and above.  See
     @{"AmigaDOS 2.0 (and above)" Link "AmigaDOS 2.0 (and above)" }.

@{b }gadgets.e@{ub } 
     The gadgets example.  See @{"Gadgets" Link "Gadgets" }.

@{b }idcmp.e@{ub } 
     The IDCMP and gadgets example.  See @{"IDCMP Messages" Link "IDCMP Messages" }.

@{b }graphics.e@{ub } 
     The graphics example.  See @{"Graphics" Link "Graphics" }.

@{b }screens.e@{ub } 
     The screens example, without an exception handler.  See @{"Screens" Link "Screens" }.

@{b }screens2.e@{ub } 
     The screens example again, but this time with an exception handler.
     See @{"Screens" Link "Screens" }.

@{b }dragon.e@{ub } 
     The dragon curve recursion example.  See @{"Recursion Example" Link "Recursion Example" }.


@ENDNODE

@NODE "String Handling and I-O" "String Handling and I-O"
@Next "Timing Expressions" 
@Prev "main" 
@Toc "Contents.guide/main" 

String Handling and I/O
***********************

   This chapter shows how to use normal strings and E-strings, and also
how to read data from a file.  The programs use a number of the string
functions and make effective (but different) use of memory where possible.
The key points to understand are:

   @{b }*@{ub } The difference between normal strings and E-strings.

   @{b }*@{ub } The two methods of reading data from a file (line-by-line or all at
     once).

   @{b }*@{ub } The necessary allocation of memory for E-strings.

   @{b }*@{ub } The unnecessary, but advisable, deallocation of the E-string memory
     once it is no longer needed.  The deallocation could be left to the
     automatic deallocation at the end of the program, but that would
     waste an increasing amount of memory whilst the program was running.
     If the input data was large then memory could easily be exhausted.

   @{b }*@{ub } The way in which sections of an E-string (or a normal string, for
     that matter) can easily be turned into normal strings.

   @{b }*@{ub } The way exception handlers can tidy up programs.

   The problem to solve is reading of a CSV (comma separated variables)
file, which is a standard format file for databases and spreadsheets.  The
format is very simple: each record is a line (i.e., terminated with a
line-feed) and each field in a record is separated by a comma.  To make
this example a lot simpler, we will forbid a field to contain a comma
(normally this would require the field to be quoted).  So, a typical input
file would look like this:

     Field1,Field2,Field3
     10,19,-3
     fred,barney,wilma
     ,,last
     first,,

In this example all records have three fields, as is well illustrated by
the first line (i.e., the first record).  The last two records may seem a
bit strange, but they just show how fields can be blank.  In the last
record all but the first field are blank, and in the previous record all
but the last are blank.

   So now we know the format of the file to be read.  To operate on a file
we must first open it using the @{b }Open@{ub } function (from the @{b }dos.library@{ub }), and
to read the lines from the file we will use the @{b }ReadStr@{ub } (built-in)
function.  There will be four versions of a program to read a CSV file:
two of which read data line-by-line and two which read all the file at
once.  Of the two which read line-by-line, one manipulates the read lines
as E-strings and the other uses normal strings.  The use of normal strings
is arguably more advanced than the use of E-strings, since cunning tricks
are employed to make effective use of memory.  However, the programs are
not meant to show that E-strings are better than normal strings (or vice
versa), rather they are meant to show how to use strings properly.

     /* A suitably large size for the record buffer */
     CONST BUFFERSIZE=512
     
     PROC main()
       DEF filehandle, status, buffer[BUFFERSIZE]:STRING, filename
       filename:='datafile'
       IF filehandle:=Open(filename, OLDFILE)
         REPEAT
           status:=ReadStr(filehandle, buffer)
           /* This is the way to check ReadStr() actually read something */
           IF buffer[] OR (status<>-1) THEN process_record(buffer)
         UNTIL status=-1
         /* If Open() succeeded then we must Close() the file */
         Close(filehandle)
       ELSE
         WriteF('Error: Failed to open "\\s"\\n', filename)
       ENDIF
     ENDPROC
     
     PROC process_record(line)
       DEF i=1, start=0, end, len, s
       /* Show the whole line being processed */
       WriteF('Processing record: "\\s"\\n', line)
       REPEAT
         /* Find the index of a comma after the start index */
         end:=InStr(line, ',', start)
         /* Length is end index minus start index */
         len:=(IF end<>-1 THEN end ELSE EstrLen(line))-start
         IF len>0
           /* Allocate an E-string of the correct length */
           IF s:=String(len)
             /* Copy the portion of the line to the E-string s */
             MidStr(s, line, start, len)
             /* At this point we could do something useful... */
             WriteF('\\t\\d) "\\s"\\n', i, s)
             /* We've finished with the E-string so deallocate it */
             DisposeLink(s)
           ELSE
             /* It's a non-fatal error if the String() call fails */
             WriteF('\\t\\d) Memory exhausted! (len=\\d)\\n', len)
           ENDIF
         ELSE
           WriteF('\\t\\d) Empty Field\\n', i)
         ENDIF
         /* The new start is after the end we found */
         start:=end+1
         INC i
       /* Once a comma is not found we've finished */
       UNTIL end=-1
     ENDPROC

   There are a couple of points worth noting about this program:

   @{b }*@{ub } A large E-string, @{b }buffer@{ub }, is used to hold each line before it is
     processed.  If a record exceeds the size of this E-string then
     @{b }ReadStr@{ub } will only read a partial record, and the next @{b }ReadStr@{ub }
     will read some more this record.  However, the program considers each
     call to @{b }ReadStr@{ub } to read a whole record, so it will get the records
     slightly wrong in this case.  This is a limitation of the program and
     it should be documented so that users know to constrain themselves to
     datafiles without long lines.

   @{b }*@{ub } The file name is `hard-wired' to be @{b }datafile@{ub }.  A more flexible
     program would allow this to be passed as an argument (see
     @{"Argument Parsing" Link "Argument Parsing" }).

   @{b }*@{ub }     @{b }ReadStr@{ub } may return -1 to indicate an error (usually when the end
     of the file has been reached), but the E-string read so far may still
     be valid.  The check on the E-string and error value is the proper
     way of deciding whether @{b }ReadStr@{ub } actually read anything from the file.

   @{b }*@{ub } Look carefully at the manipulation of the string indexes @{b }start@{ub } and
     @{b }end@{ub }, and the calculation of the length of a portion of a string.

   @{b }*@{ub }     @{b }MidStr@{ub } is used to copy a field from a record, so an E-string must
     be used to hold the field.

   @{b }*@{ub } The E-string @{b }s@{ub } is only valid between the successful allocation by
     @{b }string@{ub } and the @{b }DisposeLink@{ub }.  It would be incorrect to try to, for
     instance, print it at any other point.  On the other hand, a more
     complicated program may want to store up all the data, and so it may
     be inappropriate to deallocate the E-string at this point.  In this
     case, the pointer to the E-string could be stored and it might be
     valid for the rest of the program.

   @{b }*@{ub } The allocation using @{b }String@{ub } is very closely followed by deallocation
     using @{b }DisposeLink@{ub }.  This suggests that a single E-string could be
     allocated and used repeatedly (like @{b }buffer@{ub } is), due to the simple
     nature of this example.

   To change this to use normal strings (in a very memory efficient way),
we need to alter only the @{b }process_record@{ub } procedure.  Some note-worthy
differences are:

   @{b }*@{ub } Small parts of the E-string @{b }buffer@{ub } are turned into normal strings by
     terminating them with @{b }NIL@{ub } when necessary.  This involves changing a
     comma that is found.

   @{b }*@{ub } No new memory is allocated, rather the @{b }buffer@{ub } memory is reused (as
     described above).  This is fine for this example, although if the
     fields were needed after a record had been processed they would need
     to be copied, since the contents of @{b }buffer@{ub } are changed by @{b }ReadStr@{ub }.

     PROC process_record(line)
       DEF i=1, start=0, end, s
       /* Show the whole line being processed */
       WriteF('Processing record: "\\s"\\n', line)
       REPEAT
         /* Find the index of a comma after the start index */
         end:=InStr(line, ',', start)
         /* If a comma was found then terminate with a NIL */
         IF end<>-1 THEN line[end]:=NIL
         /* Point to the start of the field */
         s:=line+start
         IF s[]
           /* At this point we could do something useful... */
           WriteF('\\t\\d) "\\s"\\n', i, s)
         ELSE
           WriteF('\\t\\d) Empty Field\\n', i)
         ENDIF
         /* The new start is after the end we found */
         start:=end+1
         INC i
       /* Once a comma is not found we've finished */
       UNTIL end=-1
     ENDPROC

   The next two versions of the program are basically the same: they both
read the whole file into one large, dynamically allocated buffer and then
process the data.  The second of the two versions also uses exceptions to
make the program much more readable.  The differences from the above
version which uses normal strings are:

   @{b }*@{ub } The @{b }main@{ub } procedure calculates the length of the data in the file and
     then uses @{b }New@{ub } to dynamically allocate some memory to hold it.

   @{b }*@{ub } The read data is terminated with a @{b }NIL@{ub } so that it can safely be
     treated as a (very long) normal string.

   @{b }*@{ub } The @{b }process_buffer@{ub } procedure splits the read data up into lots of
     normal strings, one for each line of data.

     PROC main()
       DEF buffer, filehandle, len, filename
       filename:='datafile'
       /* Get the length of data in the file */
       IF 0<(len:=FileLength(filename))
         /* Allocate just enough room for the data + a terminating NIL */
         IF buffer:=New(len+1)
           IF filehandle:=Open(filename, OLDFILE)
             /* Read whole file, checking amount read */
             IF len=Read(filehandle, buffer, len)
               /* Terminate buffer with a NIL just in case... */
               buffer[len]:=NIL
               process_buffer(buffer, len)
             ELSE
               WriteF('Error: File reading error\\n')
             ENDIF
             /* If Open() succeeded then we must Close() the file */
             Close(filehandle)
           ELSE
             WriteF('Error: Failed to open "\\s"\\n', filename)
           ENDIF
           /* Deallocate buffer (not really necessary in this example) */
           Dispose(buffer)
         ELSE
           WriteF('Error: Insufficient memory to load file\\n')
         ENDIF
       ELSE
         WriteF('Error: "\\s" is an empty file\\n', filename)
       ENDIF
     ENDPROC
     
     /* buffer is like a normal string since it's NIL-terminated */
     PROC process_buffer(buffer, len)
       DEF start=0, end
       REPEAT
         /* Find the index of a linefeed after the start index */
         end:=InStr(buffer, '\\n', start)
         /* If a linefeed was found then terminate with a NIL */
         IF end<>-1 THEN buffer[end]:=NIL
         process_record(buffer+start)
         start:=end+1
       /* We've finished if at the end or no more linefeeds */
       UNTIL (start>=len) OR (end=-1)
     ENDPROC
     
     PROC process_record(line)
       DEF i=1, start=0, end, s
       /* Show the whole line being processed */
       WriteF('Processing record: "\\s"\\n', line)
       REPEAT
         /* Find the index of a comma after the start index */
         end:=InStr(line, ',', start)
         /* If a comma was found then terminate with a NIL */
         IF end<>-1 THEN line[end]:=NIL
         /* Point to the start of the field */
         s:=line+start
         IF s[]
           /* At this point we could do something useful... */
           WriteF('\\t\\d) "\\s"\\n', i, s)
         ELSE
           WriteF('\\t\\d) Empty Field\\n', i)
         ENDIF
         /* The new start is after the end we found */
         start:=end+1
         INC i
       /* Once a comma is not found we've finished */
       UNTIL end=-1
     ENDPROC

   The program is now quite messy, with many error cases in the @{b }main@{ub }
procedure.  We can very simply change this by using an exception handler
and a few automatic exceptions.

     /* Some constants for exceptions (ERR_NONE is zero: no error) */
     ENUM ERR_NONE, ERR_LEN, ERR_NEW, ERR_OPEN, ERR_READ
     
     /* Make some exceptions automatic */
     RAISE ERR_LEN  IF FileLength()<=0,
           ERR_NEW  IF New()=NIL,
           ERR_OPEN IF Open()=NIL
     
     PROC main() HANDLE
       /* Note the careful initialisation of buffer and filehandle */
       DEF buffer=NIL, filehandle=NIL, len, filename
       filename:='datafile'
       /* Get the length of data in the file */
       len:=FileLength(filename)
       /* Allocate just enough room for the data + a terminating NIL */
       buffer:=New(len+1)
       filehandle:=Open(filename, OLDFILE)
       /* Read whole file, checking amount read */
       IF len<>Read(filehandle, buffer, len) THEN Raise(ERR_READ)
       /* Terminate buffer with a NIL just in case... */
       buffer[len]:=NIL
       process_buffer(buffer, len)
     EXCEPT DO
       /* Both of these are safe thanks to the initialisations */
       IF buffer THEN Dispose(buffer)
       IF filehandle THEN Close(filehandle)
       /* Report error (if there was one) */
       SELECT exception
       CASE ERR_LEN;   WriteF('Error: "\\s" is an empty file\\n', filename)
       CASE ERR_NEW;   WriteF('Error: Insufficient memory to load file\\n')
       CASE ERR_OPEN;  WriteF('Error: Failed to open "\\s"\\n', filename)
       CASE ERR_READ;  WriteF('Error: File reading error\\n')
       ENDSELECT
     ENDPROC

The code is now much clearer, and the majority of errors can be caught
automatically.  Notice that the exception handler is called even if the
program succeeds (thanks to the @{b }DO@{ub } after the @{b }EXCEPT@{ub }).  This is because
when the program terminates it needs to deallocate the resources it
allocated in every case (successful or otherwise), so the code is the same.
Conditional deallocation (of the buffer, for example) is made safe by an
appropriate initialisation.

   If you feel like a small exercise, try to write a similar program but
this time using the @{b }tools/file@{ub } module which comes in the standard Amiga E
distribution.  Of course, you'll first need to read the accompanying
documentation, but you should find that this module makes file interaction
very simple.


@ENDNODE

@NODE "Timing Expressions" "Timing Expressions"
@Next "Argument Parsing" 
@Prev "String Handling and I-O" 
@Toc "Contents.guide/main" 

Timing Expressions
******************

   You may recall the outline of a timing procedure in Part Two (see
@{"Evaluation" Link "MoreExpressions.guide/Evaluation" }).  This chapter gives the complete version of this example.
The information missing from the outline was how to determine the system
time and use this to calculate the time taken by calls to @{b }Eval@{ub }.  So the
things to notice about this example are:

   @{b }*@{ub } Use of the Amiga system function @{b }DateStamp@{ub } (from the @{b }dos.library@{ub }).
     (You really need the `Rom Kernel Reference Manuals' and the `AmigaDOS
     Manual' to understand the system functions.)

   @{b }*@{ub } Use of the module @{b }dos/dos@{ub } to include the definitions of the object
     @{b }datestamp@{ub } and the constant @{b }TICKS_PER_SECOND@{ub }.  (There are fifty ticks
     per second.)

   @{b }*@{ub } Use of the @{b }repeat@{ub } procedure to do @{b }Eval@{ub } a decent number of times for
     each expression (so that some time is taken up by the calls!).

   @{b }*@{ub } The timing of the evaluation of 0, to calculate the overhead of the
     procedure calls and loop.  This value is stored in the variable
     @{b }offset@{ub } the first time the @{b }test@{ub } procedure is called.  The
     expression 0 should take a negligible amount of time, so the number
     of ticks timed is actually the time taken by the procedure calls and
     loop calculations.  Subtracting this time from the other times gives
     a fair view of how long the expressions take, relative to one another.
     (Thanks to Wouter for this offset idea.)

   @{b }*@{ub } Use of @{b }Forbid@{ub } and @{b }Permit@{ub } to turn off multi-tasking temporarily,
     making the CPU calculate only the expressions (rather than dealing
     with screen output, other programs, etc.).

   @{b }*@{ub } Use of @{b }CtrlC@{ub } and @{b }CleanUp@{ub } to allow the user to stop the program if it
     gets too boring...

   @{b }*@{ub } Use of the option @{b }LARGE@{ub } (using @{b }OPT@{ub }) to produce an executable that
     uses the large data and code model.  This seems to help make the
     timings less susceptible variations due to, for instance,
     optimisations, and so better for comparison.  See the `Reference
     Manual' for more details.

   Also supplied are some example outputs.  The first was from an A1200
with 2MB Chip RAM and 4MB Fast RAM.  The second was from an A500Plus with
2MB Chip RAM.  Both used the constant @{b }LOTS_OF_TIMES@{ub } as 500,000, but you
might need to increase this number to compare, for instance, an A4000/040
to an A4000/030.  However, 500,000 gives a pretty long wait for results on
the A500.

     MODULE 'dos/dos'
     
     CONST TICKS_PER_MINUTE=TICKS_PER_SECOND*60, LOTS_OF_TIMES=500000
     
     DEF x, y, offset
     
     PROC fred(n)
       DEF i
       i:=n+x
     ENDPROC
     
     /* Repeat evaluation of an expression */
     PROC repeat(exp)
       DEF i
       FOR i:=0 TO LOTS_OF_TIMES
         Eval(exp)  /* Evaluate the expression */
       ENDFOR
     ENDPROC
     
     /* Time an expression, and set-up offset if not done already */
     PROC test(exp, message)
       DEF t
       IF offset=0 THEN offset:=time(`0)  /* Calculate offset */
       t:=time(exp)
       WriteF('\\s:\\t\\d ticks\\n', message, t-offset)
     ENDPROC
     
     /* Time the repeated calls, and calculate number of ticks */
     PROC time(x)
       DEF ds1:datestamp, ds2:datestamp
       Forbid()
       DateStamp(ds1)
       repeat(x)
       DateStamp(ds2)
       Permit()
       IF CtrlC() THEN CleanUp(1)
     ENDPROC ((ds2.minute-ds1.minute)*TICKS_PER_MINUTE)+ds2.tick-ds1.tick
     
     PROC main()
       x:=9999
       y:=1717
       test(`x+y,     'Addition')
       test(`y-x,     'Subtraction')
       test(`x*y,     'Multiplication')
       test(`x/y,     'Division')
       test(`x OR y,  'Bitwise OR')
       test(`x AND y, 'Bitwise AND')
       test(`x=y,     'Equality')
       test(`x<y,     'Less than')
       test(`x<=y,    'Less than or equal')
       test(`y:=1,    'Assignment of 1')
       test(`y:=x,    'Assignment of x')
       test(`y++,     'Increment')
       test(`IF FALSE THEN y ELSE x, 'IF FALSE')
       test(`IF TRUE THEN y ELSE x,  'IF TRUE')
       test(`IF x THEN y ELSE x,     'IF x')
       test(`fred(2),  'fred(2)')
     ENDPROC

Here's the output from the A1200:

     Addition:	22 ticks
     Subtraction:	22 ticks
     Multiplication:	69 ticks
     Division:	123 ticks
     Bitwise OR:	33 ticks
     Bitwise AND:	27 ticks
     Equality:	44 ticks
     Less than:	43 ticks
     Less than or equal:	70 ticks
     Assignment of 1:	9 ticks
     Assignment of x:	38 ticks
     Increment:	23 ticks
     IF FALSE:	27 ticks
     IF TRUE:	38 ticks
     IF x:	44 ticks
     fred(2):	121 ticks

Compare this to the output from the A500Plus:

     Addition:	118 ticks
     Subtraction:	117 ticks
     Multiplication:	297 ticks
     Division:	643 ticks
     Bitwise OR:	118 ticks
     Bitwise AND:	117 ticks
     Equality:	164 ticks
     Less than:	164 ticks
     Less than or equal:	164 ticks
     Assignment of 1:	60 ticks
     Assignment of x:	102 ticks
     Increment:	134 ticks
     IF FALSE:	118 ticks
     IF TRUE:	164 ticks
     IF x:	193 ticks
     fred(2):	523 ticks

Evidence, if it were needed, that the A1200 is roughly five times faster
than an A500, and that's not using the special 68020 CPU instructions!


@ENDNODE

@NODE "Argument Parsing" "Argument Parsing"
@Next "Gadgets IDCMP and Graphics" 
@Prev "Timing Expressions" 
@Toc "Contents.guide/main" 

Argument Parsing
****************

   There are two examples in this chapter.  One is for any AmigaDOS and
the other is for AmigaDOS 2.0 and above.  They both illustrate how to
parse the arguments to your program.  If your program is started from the
Shell/CLI the arguments follow the command name on the command line, but
if it was started from Workbench (i.e., you double-clicked on an icon for
the program) then the arguments are those icons that were also selected at
that time (see your Workbench manual for more details).


 @{" Any AmigaDOS " Link "Any AmigaDOS" } 
 @{" AmigaDOS 2.0 (and above) " Link "AmigaDOS 2.0 (and above)" } 


@ENDNODE

@NODE "Any AmigaDOS" "Any AmigaDOS"
@Next "AmigaDOS 2.0 (and above)" 
@Toc "Argument Parsing" 

Any AmigaDOS
============

   This first example works with any AmigaDOS.  The first thing that is
done is the assignment of @{b }wbmessage@{ub } to a correctly typed pointer.  At the
same time we can check to see if it is @{b }NIL@{ub } (i.e., whether the program was
started from Workbench or not).  If it was not started from Workbench the
arguments in @{b }arg@{ub } are printed.  Otherwise we need to use the fact that
@{b }wbmessage@{ub } is really a pointer to a @{b }wbstartup@{ub } object (defined in module
@{b }workbench/startup@{ub }), so we can get at the argument list.  Then for each
argument in the list we need to check the lock supplied with the argument.
If it's a proper lock it will be a lock on the directory containing the
argument file.  The name in the argument is just a filename, not a
complete path, so to read the file we need to change the current directory
to the lock directory.  Once we've got a valid lock and we've changed
directory to there, we can find the length of the file (using @{b }FileLength@{ub })
and print it.  If there was no lock or the file did not exist, the name of
the file and an appropriate error message is printed.

     MODULE 'workbench/startup'
     
     PROC main()
       DEF startup:PTR TO wbstartup, args:PTR TO wbarg, i, oldlock, len
       IF (startup:=wbmessage)=NIL
         WriteF('Started from Shell/CLI\\n   Arguments: "\\s"\\n', arg)
       ELSE
         WriteF('Started from Workbench\\n')
         args:=startup.arglist
         FOR i:=1 TO startup.numargs  /* Loop through the arguments */
           IF args[].lock=NIL
             WriteF('  Argument \\d: "\\s" (no lock)\\n', i, args[].name)
           ELSE
             oldlock:=CurrentDir(args[].lock)
             len:=FileLength(args[].name)  /* Do something with file */
             IF len=-1
               WriteF('  Argument \\d: "\\s" (file does not exist)\\n',
                      i, args[].name)
             ELSE
               WriteF('  Argument \\d: "\\s", file length is \\d bytes\\n',
                      i, args[].name, len)
             ENDIF
             CurrentDir(oldlock) /* Important: restore current dir */
           ENDIF
           args++
         ENDFOR
       ENDIF
     ENDPROC

When you run this program you'll notice a slight difference between @{b }arg@{ub }
and the Workbench message: @{b }arg@{ub } does not contain the program name, just the
arguments, whereas the first argument in the Workbench argument list is
the program.  You can simply ignore the first Workbench argument in the
list if you want.


@ENDNODE

@NODE "AmigaDOS 2.0 (and above)" "AmigaDOS 2.0 (and above)"
@Prev "Any AmigaDOS" 
@Toc "Argument Parsing" 

AmigaDOS 2.0 (and above)
========================

   This second program can be used as the Shell/CLI part of the previous
program to provide much better command line parsing.  It can only be used
with AmigaDOS 2.0 and above (i.e., @{b }OSVERSION@{ub } which is 37 or more).  The
template @{b }FILE/M@{ub } used with @{b }ReadArgs@{ub } gives command line parsing similar to
C's @{b }argv@{ub } array.  The template can be much more interesting than this, but
for more details you need the `AmigaDOS Manual'.

     OPT OSVERSION=37
     
     PROC main()
       DEF templ, rdargs, args=NIL:PTR TO LONG, i
       IF wbmessage=NIL
         WriteF('Started from Shell/CLI\\n')
         templ:='FILE/M'
         rdargs:=ReadArgs(templ,{args},NIL)
         IF rdargs
           IF args
             i:=0
             WHILE args[i]  /* Loop through arguments */
               WriteF('   Argument \\d: "\\s"\\n', i, args[i])
               i++
             ENDWHILE
           ENDIF
           FreeArgs(rdargs)
         ENDIF
       ENDIF
     ENDPROC

As you can see the result of the @{b }ReadArgs@{ub } call with this template is an
array of filenames.  The special quoting of filenames is dealt with
correctly (i.e., when you use " around a filename that contains spaces).
You need to do all this kind of work yourself if you use the @{b }arg@{ub } method.


@ENDNODE

@NODE "Gadgets IDCMP and Graphics" "Gadgets IDCMP and Graphics"
@Next "Recursion Example" 
@Prev "Argument Parsing" 
@Toc "Contents.guide/main" 

Gadgets, IDCMP and Graphics
***************************

   There are three examples in this chapter.  The first shows how to open
a window and put some gadgets on it.  The second shows how to decipher
Intuition messages that arrive via IDCMP.  The third draws things with the
graphics functions.


 @{" Gadgets " Link "Gadgets" } 
 @{" IDCMP Messages " Link "IDCMP Messages" } 
 @{" Graphics " Link "Graphics" } 
 @{" Screens " Link "Screens" } 


@ENDNODE

@NODE "Gadgets" "Gadgets"
@Next "IDCMP Messages" 
@Toc "Gadgets IDCMP and Graphics" 

Gadgets
=======

   The following program illustrates how to create a gadget list and use
it:

     MODULE 'intuition/intuition'
     
     CONST GADGETBUFSIZE = 4 * GADGETSIZE
     
     PROC main()
       DEF buf[GADGETBUFSIZE]:ARRAY, next, wptr
       next:=Gadget(buf,  NIL, 1, 0, 10, 30, 50, 'Hello')
       next:=Gadget(next, buf, 2, 3, 70, 30, 50, 'World')
       next:=Gadget(next, buf, 3, 1, 10, 50, 50, 'from')
       next:=Gadget(next, buf, 4, 0, 70, 50, 70, 'gadgets')
       wptr:=OpenW(20,50,200,100, 0, WFLG_ACTIVATE,
                   'Gadgets in a window',NIL,1,buf)
       IF wptr         /* Check to see we opened a window */
         Delay(500)    /* Wait a bit */
         CloseW(wptr)  /* Close the window */
       ELSE
         WriteF('Error -- could not open window!')
       ENDIF
     ENDPROC

Four gadgets are created using an appropriately sized array as the buffer.
These gadgets are passed to @{b }OpenW@{ub } (the last parameter).  If the window
could be opened a small delay is used so that the window is visible before
the program closes it and terminates.  @{b }Delay@{ub } is an Amiga system function
from the DOS library, and @{b }Delay(n)@{ub } waits n/50 seconds.  Therefore, the
window stays up for 10 seconds, which is enough time to play with the
gadgets and see what the different types are.  The next example will show
a better way of deciding when to terminate the program (using the standard
close gadget).


@ENDNODE

@NODE "IDCMP Messages" "IDCMP Messages"
@Next "Graphics" 
@Prev "Gadgets" 
@Toc "Gadgets IDCMP and Graphics" 

IDCMP Messages
==============

   This next program shows how to use @{b }WaitIMessage@{ub } with a gadget.

     MODULE 'intuition/intuition'
     
     CONST GADGETBUFSIZE = GADGETSIZE, OURGADGET = 1
     
     PROC main()
       DEF buf[GADGETBUFSIZE]:ARRAY, wptr, class, gad:PTR TO gadget
       Gadget(buf, NIL, OURGADGET, 1, 10, 30, 100, 'Press Me')
       wptr:=OpenW(20,50,200,100,
                   IDCMP_CLOSEWINDOW OR IDCMP_GADGETUP,
                   WFLG_CLOSEGADGET OR WFLG_ACTIVATE,
                   'Gadget message window',NIL,1,buf)
       IF wptr              /* Check to see we opened a window */
         WHILE (class:=WaitIMessage(wptr))<>IDCMP_CLOSEWINDOW
           gad:=MsgIaddr()  /* Our gadget clicked? */
           IF (class=IDCMP_GADGETUP) AND (gad.userdata=OURGADGET)
             TextF(10,60,
                   IF gad.flags=0 THEN 'Gadget off ' ELSE 'Gadget on   ')
           ENDIF
         ENDWHILE
         CloseW(wptr)       /* Close the window */
       ELSE
         WriteF('Error -- could not open window!')
       ENDIF
     ENDPROC

The gadget reports its state when you click on it, using the @{b }TextF@{ub }
function (see @{"Graphics functions" Link "BuiltIns.guide/Graphics functions" }).  The only way to quit the program is
using the close gadget of the window.  The @{b }gadget@{ub } object is defined in the
module @{b }intuition/intuition@{ub } and the @{b }iaddr@{ub } part of the IDCMP message is a
pointer to our gadget if the message was a gadget message.  The @{b }userdata@{ub }
element of the gadget identifies the gadget that was clicked, and the
@{b }flags@{ub } element is zero if the boolean gadget is off (unselected) or
non-zero if the boolean gadget is on (selected).


@ENDNODE

@NODE "Graphics" "Graphics"
@Next "Screens" 
@Prev "IDCMP Messages" 
@Toc "Gadgets IDCMP and Graphics" 

Graphics
========

   The following program illustrates how to use the various graphics
functions.

     MODULE 'intuition/intuition'
     
     PROC main()
       DEF wptr, i
       wptr:=OpenW(20,50,200,100,IDCMP_CLOSEWINDOW,
                   WFLG_CLOSEGADGET OR WFLG_ACTIVATE,
                   'Graphics demo window',NIL,1,NIL)
       IF wptr  /* Check to see we opened a window */
         Colour(1,3)
         TextF(20,30,'Hello World')
         SetTopaz(11)
         TextF(20,60,'Hello World')
         FOR i:=10 TO 150 STEP 8  /* Plot a few points */
           Plot(i,40,2)
         ENDFOR
         Line(160,40,160,70,3)
         Line(160,70,170,40,2)
         Box(10,75,160,85,1)
         WHILE WaitIMessage(wptr)<>IDCMP_CLOSEWINDOW
         ENDWHILE
         CloseW(wptr)
       ELSE
         WriteF('Error -- could not open window!\\n')
       ENDIF
     ENDPROC

First of all a small window is opened with a close gadget and activated
(so it is the selected window).  Clicks on the close gadget will be
reported via IDCMP, and this is the only way to quit the program.  The
graphics functions are used as follows:

   @{b }*@{ub }     @{b }Colour@{ub } is used to set the foreground colour to pen one and the
     background colour to pen three.  This will make the text nicely
     highlighted.

   @{b }*@{ub } Text is output in the standard font.

   @{b }*@{ub } The font is set to Topaz 11.

   @{b }*@{ub } More text is output (probably now in a different font).

   @{b }*@{ub } The @{b }FOR@{ub } loop plots a dotted line in pen two.

   @{b }*@{ub } A vertical line in pen three is drawn.

   @{b }*@{ub } A diagonal line in pen two is drawn.  This and the previous line
     together produce a vee shape.

   @{b }*@{ub } A filled box is drawn in pen one.


@ENDNODE

@NODE "Screens" "Screens"
@Prev "Graphics" 
@Toc "Gadgets IDCMP and Graphics" 

Screens
=======

   This next example uses parts of the previous example, but also opens a
custom screen.  Basically, it draws coloured lines and boxes in a big
window opened on a 16 colour, high resolution screen.

     MODULE 'intuition/intuition', 'graphics/view'
     
     PROC main()
       DEF sptr=NIL, wptr=NIL, i
       sptr:=OpenS(640,200,4,V_HIRES,'Screen demo')
       IF sptr
         wptr:=OpenW(0,20,640,180,IDCMP_CLOSEWINDOW,
                     WFLG_CLOSEGADGET OR WFLG_ACTIVATE,
                     'Graphics demo window',sptr,$F,NIL)
         IF wptr
           TextF(20,20,'Hello World')
           FOR i:=0 TO 15  /* Draw a line and box in each colour */
             Line(20,30,620,30+(7*i),i)
             Box(10+(40*i),140,30+(40*i),170,1)
             Box(11+(40*i),141,29+(40*i),169,i)
           ENDFOR
           WHILE WaitIMessage(wptr)<>IDCMP_CLOSEWINDOW
           ENDWHILE
           WriteF('Program finished successfully\\n')
         ELSE
           WriteF('Could not open window\\n')
         ENDIF
       ELSE
         WriteF('Could not open screen\\n')
       ENDIF
       IF wptr THEN CloseW(wptr)
       IF sptr THEN CloseS(sptr)
     ENDPROC

As you can see, the error-checking @{b }IF@{ub } blocks can make the program hard to
read.  Here's the same example written with an exception handler:

     MODULE 'intuition/intuition', 'graphics/view'
     
     ENUM WIN=1, SCRN
     
     RAISE WIN  IF OpenW()=NIL,
           SCRN IF OpenS()=NIL
     
     PROC main() HANDLE
       DEF sptr=NIL, wptr=NIL, i
       sptr:=OpenS(640,200,4,V_HIRES,'Screen demo')
       wptr:=OpenW(0,20,640,180,IDCMP_CLOSEWINDOW,
                   WFLG_CLOSEGADGET OR WFLG_ACTIVATE,
                   'Graphics demo window',sptr,$F,NIL)
       TextF(20,20,'Hello World')
       FOR i:=0 TO 15  /* Draw a line and box in each colour */
         Line(20,30,620,30+(7*i),i)
         Box(10+(40*i),140,30+(40*i),170,1)
         Box(11+(40*i),141,29+(40*i),169,i)
       ENDFOR
       WHILE WaitIMessage(wptr)<>IDCMP_CLOSEWINDOW
       ENDWHILE
     EXCEPT DO
       IF wptr THEN CloseW(wptr)
       IF sptr THEN CloseS(sptr)
       SELECT exception
       CASE 0
         WriteF('Program finished successfully\\n')
       CASE WIN
         WriteF('Could not open window\\n')
       CASE SCRN
         WriteF('Could not open screen\\n')
       ENDSELECT
     ENDPROC

It's much easier to see what's going on here.  The real part of the
program (the bit before the @{b }EXCEPT@{ub }) is no longer cluttered with error
checking, and it's easy to see what happens if an error occurs.  Notice
that if the program successfully finishes it still has to close the screen
and window properly, so it's often sensible to use @{b }EXCEPT DO@{ub } to raise a
zero exception and deal with all the tidying up in the handler.


@ENDNODE

@NODE "Recursion Example" "Recursion Example"
@Next "Appendices.guide/main" 
@Prev "Gadgets IDCMP and Graphics" 
@Toc "Contents.guide/main" 

Recursion Example
*****************

   This next example uses a pair of mutually recursive procedures to draw
what is known as a dragon curve (a pretty, space-filling pattern).

     MODULE 'intuition/intuition', 'graphics/view'
     
     /* Screen size, use SIZEY=512 for a PAL screen */
     CONST SIZEX=640, SIZEY=400
     
     /* Exception values */
     ENUM WIN=1, SCRN, STK, BRK
     
     /* Directions (DIRECTIONS gives number of directions) */
     ENUM NORTH, EAST, SOUTH, WEST, DIRECTIONS
     
     RAISE WIN  IF OpenW()=NIL,
           SCRN IF OpenS()=NIL
     
     /* Start off pointing WEST */
     DEF state=WEST, x, y, t
     
     /* Face left */
     PROC left()
       state:=Mod(state-1+DIRECTIONS, DIRECTIONS)
     ENDPROC
     
     /* Move right, changing the state */
     PROC right()
       state:=Mod(state+1, DIRECTIONS)
     ENDPROC
     
     /* Move in the direction we're facing */
     PROC move()
       SELECT state
       CASE NORTH; draw(0,t)
       CASE EAST;  draw(t,0)
       CASE SOUTH; draw(0,-t)
       CASE WEST;  draw(-t,0)
       ENDSELECT
     ENDPROC
     
     /* Draw and move to specified relative position */
     PROC draw(dx, dy)
       /* Check the line will be drawn within the window bounds */
       IF (x>=Abs(dx)) AND (x<=SIZEX-Abs(dx)) AND
          (y>=Abs(dy)) AND (y<=SIZEY-10-Abs(dy))
         Line(x, y, x+dx, y+dy, 2)
       ENDIF
       x:=x+dx
       y:=y+dy
     ENDPROC
     
     PROC main() HANDLE
       DEF sptr=NIL, wptr=NIL, i, m
       /* Read arguments:        [m [t [x  [y]]]] */
       /* so you can say: dragon  16              */
       /*             or: dragon  16 1            */
       /*             or: dragon  16 1 450        */
       /*             or: dragon  16 1 450 100    */
       /* m is depth of dragon, t is length of lines */
       /* (x,y) is the start position */
       m:=Val(arg, {i})
       t:=Val(arg:=arg+i, {i})
       x:=Val(arg:=arg+i, {i})
       y:=Val(arg:=arg+i, {i})
       /* If m or t is zero use a more sensible default */
       IF m=0 THEN m:=5
       IF t=0 THEN t:=5
       sptr:=OpenS(SIZEX,SIZEY,4,V_HIRES OR V_LACE,'Dragon Curve Screen')
       wptr:=OpenW(0,10,SIZEX,SIZEY-10,
                   IDCMP_CLOSEWINDOW,WFLG_CLOSEGADGET,
                   'Dragon Curve Window',sptr,$F,NIL)
       /* Draw the dragon curve */
       dragon(m)
       WHILE WaitIMessage(wptr)<>IDCMP_CLOSEWINDOW
       ENDWHILE
     EXCEPT DO
       IF wptr THEN CloseW(wptr)
       IF sptr THEN CloseS(sptr)
       SELECT exception
       CASE 0
         WriteF('Program finished successfully\\n')
       CASE WIN
         WriteF('Could not open window\\n')
       CASE SCRN
         WriteF('Could not open screen\\n')
       CASE STK
         WriteF('Ran out of stack in recursion\\n')
       CASE BRK
         WriteF('User aborted\\n')
       ENDSELECT
     ENDPROC
     
     /* Draw the dragon curve (with left) */
     PROC dragon(m)
       /* Check stack and ctrl-C before recursing */
       IF FreeStack()<1000 THEN Raise(STK)
       IF CtrlC() THEN Raise(BRK)
       IF m>0
         dragon(m-1)
         left()
         nogard(m-1)
       ELSE
         move()
       ENDIF
     ENDPROC
     
     /* Draw the dragon curve (with right) */
     PROC nogard(m)
       IF m>0
         dragon(m-1)
         right()
         nogard(m-1)
       ELSE
         move()
       ENDIF
     ENDPROC

   If you write this to the file @{b }dragon.e@{ub } and compile it to the executable
@{b }dragon@{ub } then some good things to try are:

     dragon 5 9 300 100
     dragon 10 4 250 250
     dragon 11 3 250 250
     dragon 15 1 300 100
     dragon 16 1 400 150

   If you want to understand how the program works you need to study the
recursive parts.  Here's an overview of the program, outlining the
important aspects:

   @{b }*@{ub } The constants @{b }SIZEX@{ub } and @{b }SIZEY@{ub } are the width and height (respectively)
     of the custom screen (and window).  As the comment suggests, change
     @{b }SIZEY@{ub } to 512 if you want a bigger screen and you have a PAL Amiga.

   @{b }*@{ub } The @{b }state@{ub } variable holds the current direction (north, south, east or
     west).

   @{b }*@{ub } The @{b }left@{ub } and @{b }right@{ub } procedures turn the current direction to the left
     and right (respectively) by using some modulo arithmetic trickery.

   @{b }*@{ub } The @{b }move@{ub } procedure uses the @{b }draw@{ub } procedure to draw a line (of length
     @{b }t@{ub }) in the current direction from the current point (stored in @{b }x@{ub }
     and @{b }y@{ub }).

   @{b }*@{ub } The @{b }draw@{ub } procedure draws a line relative to the current point, but
     only if it fits within the boundaries of the window.  The current
     point is moved to the end of the line (even if it isn't drawn).

   @{b }*@{ub } The @{b }main@{ub } procedure reads the command line arguments into the
     variables @{b }m@{ub }, @{b }t@{ub }, @{b }x@{ub } and @{b }y@{ub }.  The depth/size of the dragon is given by @{b }m@{ub }
     (the first argument) and the length of each line making up the dragon
     is given by @{b }t@{ub } (the second argument).  The starting point is given by
     @{b }x@{ub } and @{b }y@{ub } (the final two arguments).  The defaults are five for @{b }m@{ub }
     and @{b }t@{ub }, and zero for @{b }x@{ub } and @{b }y@{ub }.

   @{b }*@{ub } The @{b }main@{ub } procedure also opens the screen and window, and sets the
     dragon drawing.

   @{b }*@{ub } The @{b }dragon@{ub } and @{b }nogard@{ub } procedures are very similar, and these are
     responsible for creating the dragon curve by calling the @{b }left@{ub }, @{b }right@{ub }
     and @{b }move@{ub } procedures.

   @{b }*@{ub } The @{b }dragon@{ub } procedure contains a couple of checks to see if the user
     has pressed Control-C or if the program has run out of stack space,
     raising an appropriate exception if necessary.  These exceptions are
     handled by the @{b }main@{ub } procedure.

Notice the use of @{b }Val@{ub } and the exception handling.  Also, the important
base case of the recursion is when @{b }m@{ub } reaches zero (or becomes negative,
but that shouldn't happen).  If you start off a big dragon and want to
stop it you can press Control-C and the program tidies up nicely.  If it
has finished drawing you simply click the close gadget on the window.


@ENDNODE

