home *** CD-ROM | disk | FTP | other *** search
/ Amiga Developer CD 2.1 / Amiga Developer CD v2.1.iso / Reference / DevCon / Atlanta_1990 / Atlanta-Devcon.2 / Libraries / IFFParse / Docs / iffparse.man < prev   
Encoding:
Text File  |  1992-08-26  |  39.5 KB  |  1,033 lines

  1.         IFF Parsing Library -- Documentation
  2.  
  3. 0.  Background
  4.  
  5. This documents the design and programmer interface for the low-level 
  6. IFF parsing library ("iffparse.library").  This is written for
  7. programmers, especially those who are familiar with what it takes to
  8. read and write IFF files correctly.  It also assumes that the reader is
  9. familiar with the IFF file format and it's restrictions. 
  10.  
  11. Take the time to read the EA IFF 85 specification -- it's quite 
  12. interesting, and it helps to clarify what the format is about.  After 
  13. that, read it again.  Unfortunately, there are quite a few areas that 
  14. the original specification leaves ambiguous, but we have tried to 
  15. write the library in as much of the original spirit of IFF as possible.  
  16. Programs that write bad IFF files will break.  Programmers that straddle 
  17. the line into the ambiguous areas may find their code broken too; it 
  18. can't be avoided.  Hopefully the negative impact will be minimal.
  19.  
  20. 1.  Scope
  21.  
  22. The IFF library is intended to make reading and writing IFF files easy.
  23. While it is a relatively straightforward matter to write a program to
  24. read a simple ILBM, writing code that correctly handles all the 
  25. complexity of the full IFF specification is a fairly difficult task. 
  26. The IFF parsing library is designed to deal with the general case in a
  27. way that makes the specific case easy as well. 
  28.  
  29. The IFF parsing library only deals with those elements that are common
  30. to all IFF files, such as parsing generic chunks and dealing with the
  31. proper scoping for property chunks.  It provides "hooks" (in the form of
  32. custom handlers) for any more advanced features that a programmer might
  33. need to implement. 
  34.  
  35. The IFF parsing library operates on arbitrary streams, including
  36. whatever new streams the client programmer may care to invent. 
  37. Currently DOS files and the clipboard are supported internally.  The
  38. programmer can use these types of streams or can define his own by
  39. providing functions to read, write and seek them.  The library can
  40. operate equally well on seekable (random access) streams such as disk
  41. files, and non-seekable streams such as pipes. 
  42.  
  43. 2.  Programmer Interface
  44.  
  45. Most of the functions in the parsing library operate on an instance of
  46. an IFF_File struct.  This structure is a handle on an IFF class stream
  47. which is to be read or written.  The client programmer must create,
  48. initialize and open this structure before the associated IFF file can be
  49. read or written.  The library provides some functions for doing this
  50. required setup. 
  51.  
  52. 2a.  AllocIFF() / FreeIFF()
  53.  
  54. First thing a client program must do is create an new IFF_File 
  55. structure with AllocIFF().  The calling sequence is:
  56.  
  57.     struct IFF_File *iff;
  58.  
  59.     iff = AllocIFF ();
  60.  
  61. If the result is a null pointer, the allocation failed.  An IFF_File 
  62. struct created with AllocIFF() should be disposed of with FreeIFF(), 
  63. like so:
  64.  
  65.     FreeIFF (iff);
  66.  
  67. 2b.  Stream initialization
  68.  
  69. The client program must specify what stream this raw IFF_File struct is
  70. to be associated with.  The supported stream types are DOS files and the
  71. clipboard (see the internal implementation details to see how to define
  72. your own stream types). 
  73.  
  74. 2b(i).  DOS streams
  75.  
  76. To initialize an IFF_File as a DOS stream, use the InitIFFasDOS() 
  77. function and set the iff_Stream field of the IFF_File structure to be 
  78. the BPTR to a FileHandle (the result from a call to Open()).  
  79. InitIFFasDOS() takes as arguments a pointer to the IFF_File struct to 
  80. initialize, and a mode flag to indicate if the stream is a read or 
  81. write stream.  For example, to initialize a stream for read, do:
  82.  
  83.     struct IFF_File *iff;
  84.  
  85.     iff -> iff_Stream = Open ("myfile", MODE_OLDFILE);
  86.     ... /* check for Open errors */
  87.     InitIFFasDOS (iff, IFFM_READ);
  88.  
  89. 2b(ii).  Clipboard streams
  90.  
  91. For the purpose of supporting clipboard streams, the IFF library defines
  92. a handle structure to access the clipboard.device.  Each clipboard IFF
  93. stream has one of these handles (like the FileHandle for DOS streams).
  94. The OpenClipboard() and CloseClipboard() functions create and delete 
  95. these handles and are analogous to Open() and Close() under DOS.  To 
  96. initialize an IFF_File struct as a clipboard stream, assign the 
  97. iff_Stream field in the structure to be an open clipboard handle and 
  98. call InitIFFasClip(), like so:
  99.  
  100.     struct IFF_File *iff;
  101.  
  102.     iff -> iff_Stream = OpenClipboard (PRIMARY_CLIP);
  103.     ... /* check for Open errors */
  104.     InitIFFasClip (iff);
  105.  
  106. The argument to OpenClipboard() is the clipboard "unit" number and is 
  107. normally PRIMARY_CLIP.  Notice that neither of these calls specifies the 
  108. I/O direction for the clipboard.  This is because the same clipboard 
  109. stream can be read or written many times.  To read or write to a 
  110. clipboard stream, use the BeginCB_Read()/EndCB_Read() or 
  111. BeginCB_Write()/EndCB_Write() function pairs around the reads or writes 
  112. you want to perform.
  113.  
  114. 2b(iii).  User Defined Streams
  115.  
  116. The client programmer can define his own streams by creating read, write 
  117. and seek functions for his stream and passing them to the library.  The 
  118. function for this is:
  119.  
  120.     InitIFF (iff, flags, stub, read, write, seek);
  121.  
  122. where "stub," "read," "write" and "seek" are function pointers.  Their
  123. format is described in more detail in the documentation for this
  124. function. 
  125.  
  126. The client defines the type of stream by setting the following bits in
  127. "flags": 
  128.  
  129.     IFFM_MODE    - this bit should have value IFFM_READ or 
  130.               IFFM_WRITE and indicates the I/O direction.
  131.     IFFM_FSEEK    - set this if the stream accepts forward seeks 
  132.               only.
  133.     IFFM_RSEEK    - set this if the stream accepts random access 
  134.               seeks (supercedes _FSEEK).  Non-seeking 
  135.               streams will have neither bit set.
  136.  
  137. 2c.  OpenIFF() / CloseIFF()
  138.  
  139. Once the IFF stream is ready to be read or written, the operation is
  140. initiated with OpenIFF() and ended with CloseIFF().  OpenIFF() simply
  141. prepares the IFF_File structure for a new I/O operation.  CloseIFF()
  142. cleans up the IFF_File structure, freeing anything left over from the
  143. read or write operation.  CloseIFF() can be used to end a read or write
  144. at any time. 
  145.  
  146. 2d.  Summary
  147.  
  148. In summary, there are three preparatory steps for using the IFF parsing 
  149. library -- creating an IFF_File struct, initializing the stream and 
  150. opening the IFF.  After reading or writing the IFF file, the IFF_File 
  151. structure used to do it must be closed and freed.  The steps are 
  152. summarized below (error checking has been removed for clarity -- KIDS: 
  153. don't try this in your own code!):
  154.  
  155.     struct IFF_File *iff;
  156.     /*
  157.      * DOS_Stream and Clipboard_Stream are flags for the two 
  158.      * supported stream classes.
  159.      */
  160.  
  161.     /* BEGIN */
  162.  
  163.     /*
  164.      * Get an instance of an IFF_File struct.
  165.      */
  166.     iff = AllocIFF ();
  167.  
  168.     /*
  169.      * Initialize the stream.
  170.      */
  171.     if (DOS_stream) {
  172.         iff -> iff_Stream = Open (...);
  173.         InitIFFasDOS (iff, IFFM_READ);        /* or IFFM_WRITE */
  174.     } else if (Clipboard_stream) {
  175.         iff -> iff_Stream = OpenClipboard (PRIMARY_CLIP);
  176.         InitIFFasClip (iff);
  177.     }
  178.  
  179.     /*
  180.      * Do stuff -- parse or write an IFF file.
  181.      */
  182.     OpenIFF (iff);
  183.     if (Clipboard_stream) BeginCB_Read (iff);    /* or BeginCB_Write() */
  184.  
  185.     DoStuff (iff);
  186.  
  187.     if (Clipboard_stream) EndCB_Read (iff);        /* or EndCB_Write() */
  188.     CloseIFF (iff);
  189.  
  190.     /*
  191.      * Done with IFF -- close the stream.
  192.      */
  193.     if (DOS_stream) {
  194.         Close (iff -> iff_Stream);
  195.     } else if (Clipboard_stream) {
  196.         CloseClipboard (iff -> iff_Stream);
  197.     }
  198.  
  199.     /*
  200.      * Free up IFF_File struct.
  201.      */
  202.     FreeIFF (iff);
  203.  
  204.     /* DONE */
  205.  
  206. The steps between AllocIFF() and FreeIFF() can be done any number of
  207. times for the same IFF_File structure, and in the case of the clipboard,
  208. the steps between OpenClipboard() and CloseClipboard() can be done any
  209. number of times. 
  210.  
  211. What goes on in DoStuff()?  Read on.
  212.  
  213.  
  214. 3.  Reading
  215.  
  216. Reading IFF files with the parsing library can be as simple or as
  217. complex as required.  All IFFs are parsed by calling the single function
  218. ParseIFF().  The client specifies what function he wants ParseIFF() to
  219. perform by specifying how ParseIFF() should respond when it encounters
  220. certain types of chunks in the IFF file.  This set of responses get
  221. stored in the IFF_File struct.  For a virgin IFF_File struct, the 
  222. ParseIFF() function will do nothing special, but it will still traverse 
  223. the entire file and return an error code if encounters anything odd 
  224. about the file it's traversing.  If this is a valid and syntactically 
  225. correct IFF file, ParseIFF() will return IFFERR_EOF meaning that it 
  226. reached the end of the file.
  227.  
  228. The parsing library has several predefined actions supported for dealing
  229. with chunks (more can be added with custom handlers).  The parser can be
  230. directed to halt and return to the caller when it encounters a certain
  231. type of chunk, or it can squirrel the contents of the chunk away for
  232. future use.  These two actions represent the general method for handling
  233. the two common types of chunks in IFF files -- data chunks and property
  234. chunks.  The client can say which chunks (if any) should cause these
  235. actions with StopChunk() and PropChunk(). 
  236.  
  237. 3a.  StopChunk()
  238.  
  239. StopChunk() allows the client to declare "stop" chunks, that is, chunks
  240. which cause the parser to return to the client when it enters them. Once
  241. StopChunk() is successfully called, the ParseIFF() function will return
  242. control to the client when the parser enters such a chunk.  The chunk's
  243. contents can then be processed by the calling program and parsing
  244. resumed with ParseIFF() again.  For example, suppose you wanted to read
  245. a color map out of an ILBM.  This means that you would want to search
  246. through an IFF file looking for a CMAP chunk inside a FORM ILBM.  You
  247. could use StopChunk() to do this: 
  248.  
  249.     error = StopChunk (iff, ID_ILBM, ID_CMAP);
  250.  
  251. If the return value, "error," is zero, then the specified IFF_File 
  252. struct is now defined such that ParseIFF() will return when a CMAP chunk 
  253. within an ILBM context is found.  So the call,
  254.  
  255.     error = ParseIFF (iff, IFFPARSE_SCAN);
  256.  
  257. will return zero if such a CMAP chunk is encountered, IFFERR_EOF if 
  258. there is no such chunk, or an error code if there is something wrong 
  259. with the IFF structure of the file.  If it returns zero, then the stream 
  260. associated with the IFF_File struct is positioned just at the beginning 
  261. of the data in the CMAP chunk and can be read with one of the chunk reading 
  262. functions, such as ReadChunkRecords().  So the whole sequence required 
  263. to read a CMAP out of an open IFF file is just this:
  264.  
  265.     error = StopChunk (iff, ID_ILBM, ID_CMAP);
  266.     if (error)
  267.         fatal_error ("can't declare stop chunk", error);
  268.  
  269.     error = ParseIFF (iff, IFFPARSE_SCAN);
  270.     if (error)
  271.         fatal_error ("color map not found", error);
  272.  
  273.     ncolors = ReadChunkRecords (iff, &colormap, 3, 32);
  274.     if (ncolors <= 0)
  275.         fatal_error ("error reading color map", ncolors);
  276.  
  277.     printf ("found %d colors\n", ncolors);
  278.  
  279. At this point, parsing could be resumed with another call to ParseIFF().
  280.  
  281. 3b.  PropChunk()
  282.  
  283. Now suppose you want to do something more elaborate, like read an ILBM 
  284. and display a picture.  To do this, you need to read not only the CMAP 
  285. chunk but also the BODY chunk which contains the picture data itself and 
  286. the BMHD chunk which contains parameters essential to interpreting the 
  287. BODY data.
  288.  
  289. You could do all this with StopChunk(), just setting the parser to stop 
  290. on all these types of chunks and processing each as you encounter it. 
  291. This is what many IFF readers do today, but it will not always work.  
  292. The problem, of course, is that there can be many BMHD or BODY chunks in 
  293. a single IFF file and you need to actually parse the structure to know 
  294. which header goes with which body.  Fortunately, the IFF parsing library 
  295. provides a simple way to do this through the PropChunk() handler.
  296.  
  297. In the language of IFF, BMHD and CMAP chunks are properties of the BODY 
  298. data chunk.  (CMAP is really more like a data chunk, but this is one of 
  299. the somewhat ambiguous areas of the IFF specification.)  Like 
  300. StopChunk(), PropChunk() lets the client program associate a certain 
  301. action with a certain type of chunk.  In this case, the contents of the 
  302. chunk will be stored during parsing without the client programmer 
  303. knowing about it.  At any point in the parse, the client can search for 
  304. the stored version of the chunk valid in this context with FindProp().  
  305. The parser and PropChunk() take care of managing the lexical scoping 
  306. rules for property chunks so that the data returned by FindProp() will 
  307. always be valid for the given context.
  308.  
  309. Although this may sound complex, the programmer interface is very 
  310. simple.  Here's example code to read a BODY chunk from an ILBM along 
  311. with its associated properties.
  312.  
  313.     struct IFF_File *iff;
  314.     struct StoredProperty *bmhd, *cmap;
  315.  
  316.     LONG ilbmprops[] = { ID_ILBM, ID_CMAP, ID_ILBM, ID_BMHD };
  317.  
  318.     /* IFF_File struct already allocated and initialized for read. */
  319.  
  320.     /*
  321.      * Declare the properties to store.
  322.      */
  323.     error = PropChunks (iff, ilbmprops, 2);
  324.  
  325.     /*
  326.      * Declare the chunk to stop on.
  327.      */
  328.     error = StopChunk (iff, ID_ILBM, ID_BODY);
  329.  
  330.     /*
  331.      * Parse the file, searching for the BODY chunk and storing
  332.      * the related properties on the way.
  333.      */
  334.     error = ParseIFF (iff, IFFPARSE_SCAN);
  335.  
  336.     if (error == IFFERR_EOF)
  337.         { ... no BODY chunk ... };
  338.     if (error != 0)
  339.         { ... bad IFF file ... };
  340.  
  341.     /*
  342.      * "error" was 0, so the parser is stopped at the start of the 
  343.      * BODY chunk, and the properties are stored.  Recall the stored 
  344.      * properties with FindProp().
  345.      */
  346.     bmhd = FindProp (iff, ID_ILBM, ID_BMHD);
  347.     cmap = FindProp (iff, ID_ILBM, ID_CMAP);
  348.  
  349.     /*
  350.      * If either value returned is null, then the property chunk was 
  351.      * not encountered in the IFF file before the BODY chunk.
  352.      */
  353.     if (!bmhd || !cmap)
  354.         { ... properties not found ... };
  355.  
  356.     /*
  357.      * Pass the header info and color map to another routine that 
  358.      * will read the data from the current position in the IFF 
  359.      * stream and do something with it.
  360.      */
  361.     ProcessBody (iff, bmhd->sp_Data, cmap->sp_Data, cmap->sp_Size/3);
  362.  
  363. After reading the body, the code could loop back to the ParseIFF() call
  364. and continue collecting BODY's.  The CMAP and BMHD returned by
  365. FindProp() will always be the correct ones for each BODY chunk. 
  366.  
  367. 3c.  Parse State
  368.  
  369. Suppose you declare multiple stop chunks for a single IFF file.  Once 
  370. ParseIFF() returns with a zero value, you know that the parser has 
  371. stopped at one of the chunks you wanted, but how do you tell which one?  
  372. To determine this, the library provides methods for querying the current
  373. state of the parser.  The functions CurrentChunk() and ParentChunk() get
  374. pointers to ContextNode structures which can be examined. 
  375.  
  376. CurrentChunk() returns a pointer to a context node for the chunk 
  377. currently being parsed.  For the example above, this is exactly the 
  378. chunk you are interested in.  Although much of the actual context node
  379. is private to the library there are a few public fields for finding 
  380. information about the chunk.  The cn_ID and cn_Type fields are the chunk
  381. identifier longword and the format type of the chunk, respectively, and
  382. cn_Size is the total size of this chunk in bytes. 
  383.  
  384. ParentChunk() takes a pointer to a context node and returns the pointer 
  385. to the parent node, which corresponds to the chunk which encloses the 
  386. given chunk.  For a CMAP chunk in an ILBM for example, ParentChunk() 
  387. will return the context node for the FORM ILBM chunk that contains the 
  388. CMAP chunk.
  389.  
  390. 3d.  StopOnExit()
  391.  
  392. The ILBM format is somewhat special in that it can be interpreted by 
  393. only reading one data chunk (BODY) and its associated property chunks. 
  394. Most IFF files, however, require that you read multiple data chunks 
  395. before interpreting the meaning of the FORM.  In order to facilitate 
  396. this, the library provides the StopOnExit() call for pre-defining chunks 
  397. that should cause the parser to return when they are exhausted and about 
  398. to be popped.  Calling this function for the FORM chunk itself provides 
  399. a way for the client program to be notified when all the data chunks in 
  400. a FORM have been read.
  401.  
  402. When ParseIFF() returns when at the end of a chunk, it returns the value 
  403. IFFERR_EOC rather than zero.
  404.  
  405. The following code fragment demonstrates the use of StopOnExit() and how 
  406. it can be used with StopChunk() to keep track of when the parser is 
  407. traversing a particular chunk (in this case a FORM ILBM).
  408.  
  409.     struct IFF_File *iff;
  410.     LONG error;
  411.  
  412.     /* "iff" allocated and initialized. */
  413.  
  414.     /*
  415.      * Set up the IFF_File struct to stop when entering or leaving
  416.      * FORM chunks of type ILBM.
  417.      */
  418.     StopChunk (iff, ID_ILBM, ID_FORM);
  419.     StopOnExit (iff, ID_ILBM, ID_FORM);
  420.  
  421.     while (1) {
  422.         /*
  423.          * Advance the parser to the next stop point and exit at
  424.          * end of file.  Error code determines what we found.
  425.          */
  426.         error = ParseIFF (iff, IFFPARSE_SCAN);
  427.         if (error == IFFERR_EOF) break;
  428.  
  429.         if (error == 0) {
  430.             puts ("entering FORM ILBM.");
  431.             continue;
  432.         }
  433.  
  434.         if (error == IFFERR_EOC) {
  435.             puts ("leaving FORM ILBM.");
  436.             continue;
  437.         }
  438.  
  439.         printf ("IFF error: %d\n", error);
  440.         break;
  441.     }
  442.  
  443. 3e.  ParseIFF() Control Values.
  444.  
  445. The ParseIFF() function takes a control argument.  This can have one of 
  446. three values, and sets, essentially, how much control the calling 
  447. program wants to exercise over the parsing process.  The normal control 
  448. value, IFFPARSE_SCAN, causes the function to scan the file and invoke
  449. the handlers for the chunks.  In this control mode, ParseIFF() will only
  450. return on end of file, an error, or a stop chunk. 
  451.  
  452. The other two possible control values are IFFPARSE_STEP and _RAWSTEP. 
  453. In both these modes, ParseIFF() will return every time a new chunk is
  454. entered (pushed) or just before an exhausted chunk is exited (popped).
  455. If the parser just entered a chunk, ParseIFF() returns zero, and if the
  456. parser is pausing at the end of a chunk, ParseIFF() returns IFFERR_EOC.
  457. In STEP mode, the handlers for chunks will still be called, while in
  458. RAWSTEP mode, the parser will return without calling any handlers. 
  459.  
  460. The single step modes are useful for using the IFF parsing library as 
  461. the "back end" for a custom parser (examples are 'sift.c' and 'tree.c').
  462.  
  463.  
  464. 4.  Writing Examples
  465.  
  466. Writing IFF files is somewhat more straightforward than reading them, 
  467. since you know in advance what you want to put in them and in what 
  468. order.  Initializing a stream for write depends on the stream: for 
  469. DOS files, it just means opening the file with write access; for the 
  470. clipboard it means calling BeginCB_Write().  For a custom stream type, 
  471. set the IFFM_MODE bit in the iff_Flags word to IFFM_WRITE.
  472.  
  473. Once the IFF file stream is ready to write, the client can write chunks 
  474. and their data using PushChunk(), WriteChunkBytes() or
  475. WriteChunkRecords(), and PopChunk().  PushChunk() opens a new chunk and
  476. PopChunk() closes it and chunks can nest according to the rules of IFF
  477. files.  Every IFF file is really a single chunk with all the data within
  478. as nested sub-chunks, so every IFF file is written with a single
  479. PushChunk() / PopChunk() pair surrounding all the writes. 
  480.  
  481. PushChunk() takes several arguments and returns an error code:
  482.  
  483.     LONG    PushChunk (iff, type, id, size)
  484.  
  485. The first argument is the IFF_File struct itself.  The second is the 
  486. format type code which is ignored for data chunks since they inherit 
  487. their type from their parent chunk.  The third is the chunk id code, 
  488. which must be one of FORM, CAT or LIST for the first chunk pushed.  Size 
  489. is the size of this chunk in bytes.  If this argument is 
  490. IFF_SIZEUNKNOWN, the library will compute the size of the chunk from the 
  491. data written into it.  If it has some other value, the chunk will be 
  492. forced to conform to the size given.
  493.  
  494. As a writing example, a simple bitmap can be written as a FORM chunk of
  495. type ILBM with the following sub-chunks: 
  496.  
  497.     BMHD    - bitmap header data (always first).
  498.     CMAP    - color map data.
  499.     BODY    - interleaved bitmap data (always last).
  500.  
  501. The following program might be used to write such a file:
  502.  
  503.     struct IFF_File *iff;
  504.     struct BitmapHeader *header;
  505.     LONG error, size;
  506.  
  507.     /* "iff" already allocated and stream initialized. */
  508.  
  509.     OpenIFF (iff);
  510.  
  511.     /*
  512.      * Start the all-encompassing FORM chunk for the file.
  513.      */
  514.     error = PushChunk (iff, ID_ILBM, ID_FORM, IFF_SIZEUNKNOWN);
  515.     if (error) ...fail;
  516.  
  517.     /*
  518.      * Write the BMHD chunk.  These are always the same size.
  519.      */
  520.     error = PushChunk (iff, 0, ID_BMHD, sizeof (*header));
  521.     if (error) ...fail;
  522.     size = WriteChunkBytes (iff, header, sizeof (*header));
  523.     if (size != sizeof (*header)) ...fail;
  524.     error = PopChunk (iff);
  525.     if (error) ...fail;
  526.  
  527.     /*
  528.      * Write the CMAP chunk.  Three times the number of colors.
  529.      */
  530.     error = PushChunk (iff, 0, ID_CMAP, IFF_SIZEUNKNOWN);
  531.     if (error) ...fail;
  532.     size = WriteChunkRecords (iff, colors, 3, ncolor);
  533.     if (size != ncolor) ...fail;
  534.     error = PopChunk (iff);
  535.     if (error) ...fail;
  536.  
  537.     /*
  538.      * Write the BODY chunk.  Calls WriteBODY() which would 
  539.      * presumably be a function that just calls WriteChunkBytes/Records
  540.      * many times and then returns.
  541.      */
  542.     error = PushChunk (iff, 0, ID_BODY, IFF_SIZEUNKNOWN);
  543.     if (error) ...fail;
  544.     error = WriteBODY (iff, header);
  545.     if (error) ...fail;
  546.     error = PopChunk (iff);
  547.     if (error) ...fail;
  548.  
  549.     /*
  550.      * Close the outermost FORM chunk context.  Doing this makes the 
  551.      * file effectively unwritable from this point.
  552.      */
  553.     error = PopChunk (iff);
  554.     if (error) ...fail;
  555.  
  556.     CloseIFF (iff);
  557.  
  558. You will notice that there are a lot of statements like:
  559.  
  560.     if (error) ...fail;
  561.  
  562. How to deal with errors depends on how the code is written, but in
  563. general, once an error occurs, calling CloseIFF() on the offending file
  564. will shut things down cleanly.  So the "...fail" expression could be a
  565. _goto_ passing control to the CloseIFF() call, or, if the writing calls
  566. are all kept in a subroutine, it could be a _return_ statement. 
  567.  
  568. Errors will occur not only for errors encountered writing to the stream,
  569. but also for IFF format errors.  Programmers are encouraged to observe
  570. full error checking at all times, especially during initial testing
  571. since the library may signal IFF syntax errors that are part of the
  572. program itself.  The writer is not guaranteed to catch all format
  573. errors, however, and programmers should always test their files with an
  574. IFF reader or scanner for the format they are writing. 
  575.  
  576. 5.  Orthogonality
  577.  
  578. In general, anything that works in read mode will work in write mode.
  579. The chunk context query functions, CurrentChunk() and ParentChunk(), as
  580. well as the value of the iff_Depth field, will give reasonable answers
  581. in write mode.  Chunk handlers, however, do not work in write mode. 
  582.  
  583. By the same token, PushChunk() and PopChunk() will also work in read
  584. mode.  Calling PushChunk() on a read mode file will force the parser to
  585. step into a new chunk at the given point in the file, and calling
  586. PopChunk() will force the parser to exit the current chunk, skipping
  587. past any data in the chunk that have not been read.  Generally, however,
  588. you should not mix these low-level calls with calls to the more
  589. high-level ParseIFF(). 
  590.  
  591. 6.  Custom Handlers
  592.  
  593. All the types of actions that can be performed when encountering or 
  594. leaving a certain chunk, such as those initialized with PropChunk(), 
  595. StopChunk() and StopOnExit(), are just built-in chunk handlers.  If 
  596. these actions are not complex enough, the client programmer can install
  597. his own custom handlers to perform any action he needs on entering or 
  598. exiting a chunk.  Chunk handlers are added to a given IFF_File struct 
  599. with the EntryHandler() and ExitHandler() functions:
  600.  
  601.     error = EntryHandler (iff, type, id, func, stub, pos)
  602.     error = ExitHandler (iff, type, id, func, stub, pos)
  603.  
  604. "Type" and "id," as always, indicate what chunk to handle.  "Func" is 
  605. the client supplied handler function.  This function will be called
  606. using the call-back stub, "stub."  The "pos" argument indicates where 
  607. the handler will be valid relative to the current context.
  608.  
  609. 6a.  User Call-backs Stub
  610.  
  611. The library calls user code by putting arguments in registers, but since
  612. this is not convenient for programmers writing in a high-level language
  613. such as 'C', the library allows for different calling conventions with
  614. the call-back stub.  The call-back stub for a language, GenericStub(),
  615. is a small assembly language routine that translates the register
  616. arguments to the calling convention of the host language.  GenericStub() 
  617. takes the four registers A0, A1, D0 and D1 and passes them to the actual 
  618. function being called as if they were arguments.  (The GenericStub() for 
  619. small model Aztec C programs also restores the base register A4.)
  620.  
  621. In the case of chunk handlers, A0 is a pointer to the IFF_File struct
  622. being parsed and A1 is a pointer to the ContextNode struct for this
  623. chunk.  As result, user handler functions should receive the following
  624. four arguments: 
  625.  
  626.     LONG
  627.     MyHandler (iff, chp, D0, D1)
  628.         struct IFF_File        *iff;
  629.         struct ContextNode    *chp;
  630.         LONG            D0, D1;
  631.     {
  632.         ...
  633.     }
  634.  
  635. The D0 and D1 arguments have no meaning but are included since the stub 
  636. function will provide them.
  637.  
  638. 6b.  Handler Scoping
  639.  
  640. The "pos" argument to the handler installation functions indicates the 
  641. handler's scope -- where it's valid.  There are three possible scopes: 
  642. ROOT, TOP and PROP.
  643.  
  644. A pos of IFFSLI_ROOT will install the handler into the global context 
  645. which makes it valid throughout parsing.  Any time a chunk with the
  646. given type and id is encountered, a handler in the global, or default,
  647. context will be called. 
  648.  
  649. A pos of IFFSLI_TOP will install the handler into the current context,
  650. meaning that it will only be valid as long as the current chunk is being 
  651. parsed.  It will also override any handlers installed at a lower level, 
  652. such as one for a chunk containing this chunk, or a handler installed in 
  653. the default context.  Once the current chunk ends, the handler will be 
  654. purged.
  655.  
  656. IFFSLI_PROP scoping is like the TOP option except that the handler is 
  657. valid for the nearest containing FORM or LIST chunk.  This corresponds 
  658. to the scoping for shared properties.
  659.  
  660. The ability to override other handlers is useful for scanning nested
  661. formats.  For example, the client might install default handlers for
  662. ILBM chunks to be used for interpreting a bitmap format.  But if the
  663. parser encounters a FORM ANIM, the client might want to install
  664. different handlers for ILBM chunks for the course of parsing the ANIM. 
  665. By installing handlers with IFFSLI_TOP position when first entering the
  666. FORM chunk for the ANIM, they will only be valid while the ANIM is being
  667. scanned and will automatically go away when that FORM chunk ends. 
  668.  
  669. All the built-in handler installers, such as PropChunk() and 
  670. StopChunk(), use TOP position for their handlers.  This is the same as 
  671. ROOT position if no chunk is currently being parsed, like just before or 
  672. after an OpenIFF() call.
  673.  
  674. 6c.  Handler Return Value
  675.  
  676. Chunk handlers return a value in D0 (or are defined as LONG functions in
  677. 'C').  This is their error return code.  Returning zero allows the
  678. parsing to continue normally, and any other value will cause ParseIFF()
  679. to return that value to the client as its returned error code.  The
  680. exception is the special code, IFF_RETURN2CLIENT, which will signal
  681. ParseIFF() to return to the caller but to return zero. 
  682.  
  683. If a custom chunk handler gets an error from an IFF library function, it
  684. should normally pass that error code on in D0.  A handler can in general
  685. return any value, but negative codes are reserved for library internal
  686. errors.  Unless the handler is using its return value to signal
  687. something special to the calling program, or if it caught an error, it
  688. should return zero. 
  689.  
  690. 6d.  Summary
  691.  
  692. The client program can arrange for any function to be called when the 
  693. parser enters or exits any chunk.  These functions, called "handlers," 
  694. should either accept their arguments in registers, or be set up to get
  695. their arguments translated by a stub function.  The provided stub
  696. function for each language, GenericStub(), takes the arguments A0, A1,
  697. D0, D1 and passes them in that order to the user's function.  Handlers
  698. get the two arguments: 
  699.  
  700.     A0 -- pointer to the IFF_File struct being parsed.
  701.     A1 -- pointer to the ContextNode struct for this chunk.
  702.  
  703. Handlers are installed with the ExitHandler() and EntryHandler() 
  704. functions, which specify a stub function, if any, and a context position 
  705. argument.  Entry handlers get called just after their chunk is pushed,
  706. and exit handlers get called just before their chunk is popped. 
  707.  
  708.  
  709. 7.  Internal Design - The Context Stack
  710.  
  711. At the heart of the IFF_File structure lies the Context Stack.  This is 
  712. a LIFO stack of ContextNode structures, each one corresponding to a 
  713. chunk in the current parse state.  As the parser (or writer) traverses
  714. the file, it pushes and pops context nodes on and off the context stack 
  715. to maintain an internal model of the file structure.  Arbitrary data can
  716. be associated with a context node and will be handled appropriately to
  717. its scoping context. 
  718.  
  719. Each ContextNode element on the stack has fields for chunk ID, type,
  720. size, and a count of the number of bytes already read from this chunk. 
  721. As an example of how this stack structure gets used to read IFF files,
  722. consider the simple image file (which might have been written by the 
  723. writing example):
  724.  
  725.     "FORM" {
  726.         "ILBM"
  727.         "BMHD" {20 bytes}
  728.         "CMAP" {21 bytes}
  729.         "BODY" {32000 bytes}
  730.     }
  731.  
  732. When ParseIFF() is first called, the first chunk from the file is pushed
  733. onto the stack (remember - the entire file is one chunk).  So at this 
  734. step, the stack consists of one element: 
  735.  
  736.     +----------+
  737.     |FORM 32070|
  738.     |ILBM 4    |
  739.     +----------+
  740.  
  741. This means that the current chunk being parsed has ID `FORM', type
  742. `ILBM' and contains 32070 bytes of which 4 bytes have been read (the
  743. type identifier itself).  Because FORM chunks are grouping chunks in the
  744. IFF format, the parser will push a new chunk element on the context
  745. stack, in this case, the BMHD chunk. 
  746.  
  747.     +----------+    +----------+
  748.     |BMHD 20   |    |FORM 32070|
  749.     |ILBM 0    |    |ILBM 12   |
  750.     +----------+    +----------+
  751.  
  752. At this point, the file is positioned ready to read the first data byte 
  753. in the BMHD chunk, and the context stack correctly reflects this by 
  754. showing that the top chunk is the BMHD chunk and zero bytes have been
  755. read.  The bytes read count of the parent FORM chunk has increased to 
  756. 12, since the 8 bytes required to push the BMHD chunk on the stack get 
  757. counted against the total in the FORM.  The application program or a
  758. handler could then read out the BMHD chunk's contents, whichever is more
  759. appropriate.  In either case, the chunk reading routines will update the
  760. byte count to reflect the number of bytes read. 
  761.  
  762.     +----------+    +----------+
  763.     |BMHD 20   |    |FORM 32070|
  764.     |ILBM 20   |    |ILBM 12   |
  765.     +----------+    +----------+
  766.  
  767. When the chunk is completely processed, any bytes unread will be skipped
  768. (optional pad bytes will be skipped at this point), the chunk will be
  769. popped off the stack, and it's parent context element will be updated to
  770. indicate how many bytes have been read out of it at this stage.  The top
  771. context element will always have a correct scan count, even though lower
  772. elements may be out of step -- they will be caught up when they become
  773. the top element.  After the BMHD chunk has been read, the context stack
  774. looks like this: 
  775.  
  776.     +----------+
  777.     |FORM 32070|
  778.     |ILBM 32   |
  779.     +----------+
  780.  
  781. Reading the CMAP and BODY chunks go the same way ...
  782.  
  783.     +----------+    +----------+
  784.     |CMAP 21   |    |FORM 32070|
  785.     |ILBM 0    |    |ILBM 40   |
  786.     +----------+    +----------+
  787.  
  788.     +----------+    +----------+
  789.     |BODY 32000|    |FORM 32070|
  790.     |ILBM 0    |    |ILBM 70   |
  791.     +----------+    +----------+
  792.  
  793. After everything is read, the top context element looks like this:
  794.  
  795.     +----------+
  796.     |FORM 32070|
  797.     |ILBM 32070|
  798.     +----------+
  799.  
  800. Once this main context is popped, the ParseIFF() function will return
  801. IFFERR_EOF. 
  802.  
  803. 7a.  Local Context Items
  804.  
  805. Each context node also has a list of associated items, called Local 
  806. Context Items.  These items can be anything, and are identified by an 
  807. ident code as well as an ID and type code just like chunks.  Each local
  808. context item also has its own purge function vector, so that the item
  809. can be disposed of by the library when it pops its context node. 
  810.  
  811. LocalContextItems are allocated by the client programmer with the 
  812. AllocLocalItem function.
  813.  
  814.     item = AllocContextItem (ident, type, id, usize)
  815.  
  816. The "ident" argument is the major class type of item being created. 
  817. This is just a longword identifier like chunk type and id codes, and is
  818. created the same way (but out of lower-case letters to be distinct from
  819. chunk format codes).  There are a few predefied local context class
  820. ident codes that are used internally:
  821.  
  822.     "enhd"    -- entry handler
  823.     "exhd"    -- exit handler
  824.     "prop"    -- stored property
  825.     "coll"    -- collection list
  826.  
  827. It's clear from these examples that many of the basic capabilities of
  828. the parser are implemented as special classes of local context items. 
  829. In fact the context stack and local context items underlie most of what
  830. the parser does.
  831.  
  832. The "type" and "id" arguments are two more identifying longword codes
  833. for the item being allocated.  Often these are the analogous to the type
  834. and id for chunks, but they don't have to be.  The "usize" argument is
  835. the number of bytes of use data space to allocate along with this local
  836. context item.  To get access to this block of user data space, use the
  837. LocalItemData function:
  838.  
  839.     data = LocalItemData (item)
  840.  
  841. The function returns a pointer to the user data associated with the
  842. local context item "item".  This is the only supported way to get a
  843. handle on this data.
  844.  
  845. Local context items can be attached to any context node in the context
  846. stack, as well as to the "default" context associated with an IFF_File
  847. structure.  This can be done with the StoreLocalItem or
  848. StoreItemInContext functions:
  849.  
  850.     error = StoreLocalItem (iff, item, pos)
  851.  
  852.     StoreItemInContext (iff, item, cnode)
  853.  
  854. StoreLocalItem stores an item relative to the current parse context
  855. according to the "pos" argument which can have values of IFFSLI_TOP, the 
  856. "top" or current context, IFFSLI_ROOT, the "root" or default context, or 
  857. IFFSLI_PROP, the property scope closest to the current context. 
  858. StoreItemInContext attaches an item directly to a context node given a
  859. pointer to it.
  860.  
  861. 7b.  Local Context Item Purge Vectors.
  862.  
  863. When a Context Node is popped off the context stack (i.e. when its chunk
  864. is exited) the local items associated with it get purged.  Normally, the
  865. library just calls FreeLocalItem(item) to deallocate items, but since
  866. there can be any user data also stored in the local item, the library
  867. also provides a mechanism for the user to do his own clean up of items
  868. to be purged.  The user cna set his own purge vectors with:
  869.  
  870.     SetLocalItemPurge (item, stub, purge)
  871.  
  872. The purge vector will be called (via the stub, if any) with the
  873. following arguments:
  874.  
  875.     A0 -- pointer to the IFF_File struct.
  876.     A1 -- pointer to the LocalContextItem struct to be deleted.
  877.  
  878. The user should do his own clean up and then call FreeLocalItem on the
  879. item to free it and the user data block.
  880.  
  881. Items that have been stored in the default (or root) context will not be
  882. purged until the IFF_File structure itself is disposed of with FreeIFF.
  883.  
  884. 7c.  Finding Local Context Items.
  885.  
  886. Once items have been stored in the context stack, they can be retrieved
  887. using the FindLocalItem function.  The way that FindLocalItem goes about
  888. locating the requested item allows for the transparent handling of the
  889. IFF scoping rules.
  890.  
  891.     item = FindLocalItem (iff, ident, type, id)
  892.  
  893. Given a set of ident, type and id codes to look for, this function first
  894. looks at the current context level in the context stack for an item that
  895. matches the given codes.  If none is found, it will look in the next
  896. deeper context, and then the next deeper and so on.  If it cannot find
  897. any in the stack itself it will then search the default item list.  It
  898. will return the first item it finds that matches the three code values. 
  899. If it can find none, it will return NULL.
  900.  
  901. 7d.  Stored Properties: An Example of Local Context Items.
  902.  
  903. The behavior of FindLocalItem makes dealing with stored properties
  904. almost trivial, so by way of illustration, the operation of how
  905. properties are stored will be explained with a detailed example.
  906.  
  907. What happens internally is that as the reader runs into recognized 
  908. property chunks, it stores them away as local context items.  When the
  909. user wants them, the library uses FindLocalItem to find the instance of
  910. stored property that is closest to the top of the stack and returns that
  911. to the user.  As context stack elements are popped, invalid stored
  912. property chunks are deleted and older chunks which are now valid come to 
  913. the front again.  As a specific example, take this complex IFF file:
  914.  
  915.     "LIST" {
  916.         "JUNK"
  917.         "PROP" {
  918.             "ILBM"
  919.             "BMHD" {...}
  920.             "CMAP" {...}
  921.         }
  922.         "FORM" {
  923.             "ILBM"
  924.             "BODY" {...}
  925.         }
  926.         "FORM" {
  927.             "ILBM"
  928.             "CMAP" {...}
  929.             "BODY" {...}
  930.         }
  931.     }
  932.  
  933. It is a LIST containing two ILBM FORM chunks with a PROP chunk declaring
  934. default BMHD and CMAP chunks for ILBM FORMs.  Since the user is
  935. interested in interpreting the ILBM BODY chunks with BMHD and CMAP
  936. chunks as properties, he will declare them as properties with the
  937. PropChunk function.  What this function does is install an entry handler
  938. for ILBM BMHD and ILBM CMAP chunks which will store them as local
  939. context items in a property scope.
  940.  
  941. The parsing sequence for this file starts with the context stack being 
  942. initialized to the overall chunk for the file: 
  943.  
  944.     +----------+
  945.     |LIST 64144|
  946.     |JUNK 4    |
  947.     +----------+
  948.  
  949. The parser then pushes elements for the PROP and its internal BMHD
  950. chunk. 
  951.  
  952.     +----------+    +----------+    +----------+
  953.     |BMHD 20   |    |PROP 62   |    |LIST 64144|
  954.     |ILBM 0    |    |ILBM 12   |    |JUNK 12   |
  955.     +----------+    +----------+    +----------+
  956.  
  957. Now the parser invokes the handler for this ILBM.BMHD chunk and the
  958. handler reads it itself, storing the contents of the chunk in a buffer.
  959. It then attaches this buffer to a local context item and stores it in
  960. the PROP context for this chunk.  This will be the LIST element in the 
  961. context stack, since this chunk represents the context in which this 
  962. property is valid.  Once the reader exits the LIST chunk, the property 
  963. will no longer be valid and will be automatically expunged.  The local
  964. item will have an ident of "prop" and type and id of ILBM and BMHD.
  965.  
  966.     +----------+    +----------+    +----------+
  967.     |BMHD 20   |    |PROP 62   |    |LIST 64144|
  968.     |ILBM 20   |    |ILBM 12   |    |JUNK 12   |
  969.     +----------+    +----------+    +----------+
  970.                        |
  971.                     prop ILBM.BMHD
  972.  
  973. The parser processes the CMAP chunk the same way and pops the PROP 
  974. element since it's exhausted.  The remaining LIST context element has 
  975. property chunks hanging off of it stored as local context items as it 
  976. begins to parse the next chunk.
  977.  
  978.     +----------+
  979.     |LIST 64144|
  980.     |JUNK 74   |
  981.     +----------+
  982.        |
  983.     prop ILBM.BMHD
  984.     prop ILBM.CMAP
  985.  
  986. When the parser reaches the BODY chunk, the stack looks like this:
  987.  
  988.     +----------+    +----------+    +----------+
  989.     |BODY 32000|    |FORM 32012|    |LIST 64144|
  990.     |ILBM 0    |    |ILBM 12   |    |JUNK 82   |
  991.     +----------+    +----------+    +----------+
  992.                        |
  993.                     prop ILBM.BMHD
  994.                     prop ILBM.CMAP
  995.  
  996. Since the user needs the stored properties to correctly process the BODY 
  997. chunk, he will recall the stored properties with FindProp.  This
  998. function just calls FindLocalItem for "prop" ident items with the
  999. requested type and id which finds the stored property chunks attached to 
  1000. the LIST context node.  This is the correct behavior here since this
  1001. FORM has no overriding properties.  Parsing the next ILBM FORM is
  1002. different, however.  As the parser enters this FORM, it invoke the
  1003. property handler for the CMAP chunk.
  1004.  
  1005.     +----------+    +----------+    +----------+
  1006.     |CMAP 21   |    |FORM 32042|    |LIST 64144|
  1007.     |ILBM 0    |    |ILBM 12   |    |JUNK 32102|
  1008.     +----------+    +----------+    +----------+
  1009.                        |
  1010.                     prop ILBM.BMHD
  1011.                     prop ILBM.CMAP
  1012.  
  1013. The handler reads the contents of the CMAP and attaches it to the
  1014. context element for the FORM, since that is the scope for this property. 
  1015. Now, when the reader encounters the BODY chunk and passes control to the 
  1016. user to deal with it, the user sees the correct properties for the given
  1017. context when using FindProp().
  1018.  
  1019.     +----------+    +----------+    +----------+
  1020.     |BODY 32000|    |FORM 32042|    |LIST 64144|
  1021.     |ILBM 0    |    |ILBM 34   |    |JUNK 32102|
  1022.     +----------+    +----------+    +----------+
  1023.               |           |
  1024.             prop ILBM.CMAP    prop ILBM.BMHD
  1025.                     prop ILBM.CMAP
  1026.  
  1027. When the application asks for a CMAP, it gets the overriding one -- the
  1028. one attached to the FORM element.  When it asks for a BMHD, it gets the
  1029. default one specified for this LIST.  When the context nodes get popped
  1030. as result of continuing to parse, or of the user program calling
  1031. CloseIFF, the stored properties will be purged as if they were never
  1032. even there.
  1033.