home *** CD-ROM | disk | FTP | other *** search
Text File | 1992-08-26 | 39.5 KB | 1,033 lines |
- IFF Parsing Library -- Documentation
-
- 0. Background
-
- This documents the design and programmer interface for the low-level
- IFF parsing library ("iffparse.library"). This is written for
- programmers, especially those who are familiar with what it takes to
- read and write IFF files correctly. It also assumes that the reader is
- familiar with the IFF file format and it's restrictions.
-
- Take the time to read the EA IFF 85 specification -- it's quite
- interesting, and it helps to clarify what the format is about. After
- that, read it again. Unfortunately, there are quite a few areas that
- the original specification leaves ambiguous, but we have tried to
- write the library in as much of the original spirit of IFF as possible.
- Programs that write bad IFF files will break. Programmers that straddle
- the line into the ambiguous areas may find their code broken too; it
- can't be avoided. Hopefully the negative impact will be minimal.
-
- 1. Scope
-
- The IFF library is intended to make reading and writing IFF files easy.
- While it is a relatively straightforward matter to write a program to
- read a simple ILBM, writing code that correctly handles all the
- complexity of the full IFF specification is a fairly difficult task.
- The IFF parsing library is designed to deal with the general case in a
- way that makes the specific case easy as well.
-
- The IFF parsing library only deals with those elements that are common
- to all IFF files, such as parsing generic chunks and dealing with the
- proper scoping for property chunks. It provides "hooks" (in the form of
- custom handlers) for any more advanced features that a programmer might
- need to implement.
-
- The IFF parsing library operates on arbitrary streams, including
- whatever new streams the client programmer may care to invent.
- Currently DOS files and the clipboard are supported internally. The
- programmer can use these types of streams or can define his own by
- providing functions to read, write and seek them. The library can
- operate equally well on seekable (random access) streams such as disk
- files, and non-seekable streams such as pipes.
-
- 2. Programmer Interface
-
- Most of the functions in the parsing library operate on an instance of
- an IFF_File struct. This structure is a handle on an IFF class stream
- which is to be read or written. The client programmer must create,
- initialize and open this structure before the associated IFF file can be
- read or written. The library provides some functions for doing this
- required setup.
-
- 2a. AllocIFF() / FreeIFF()
-
- First thing a client program must do is create an new IFF_File
- structure with AllocIFF(). The calling sequence is:
-
- struct IFF_File *iff;
-
- iff = AllocIFF ();
-
- If the result is a null pointer, the allocation failed. An IFF_File
- struct created with AllocIFF() should be disposed of with FreeIFF(),
- like so:
-
- FreeIFF (iff);
-
- 2b. Stream initialization
-
- The client program must specify what stream this raw IFF_File struct is
- to be associated with. The supported stream types are DOS files and the
- clipboard (see the internal implementation details to see how to define
- your own stream types).
-
- 2b(i). DOS streams
-
- To initialize an IFF_File as a DOS stream, use the InitIFFasDOS()
- function and set the iff_Stream field of the IFF_File structure to be
- the BPTR to a FileHandle (the result from a call to Open()).
- InitIFFasDOS() takes as arguments a pointer to the IFF_File struct to
- initialize, and a mode flag to indicate if the stream is a read or
- write stream. For example, to initialize a stream for read, do:
-
- struct IFF_File *iff;
-
- iff -> iff_Stream = Open ("myfile", MODE_OLDFILE);
- ... /* check for Open errors */
- InitIFFasDOS (iff, IFFM_READ);
-
- 2b(ii). Clipboard streams
-
- For the purpose of supporting clipboard streams, the IFF library defines
- a handle structure to access the clipboard.device. Each clipboard IFF
- stream has one of these handles (like the FileHandle for DOS streams).
- The OpenClipboard() and CloseClipboard() functions create and delete
- these handles and are analogous to Open() and Close() under DOS. To
- initialize an IFF_File struct as a clipboard stream, assign the
- iff_Stream field in the structure to be an open clipboard handle and
- call InitIFFasClip(), like so:
-
- struct IFF_File *iff;
-
- iff -> iff_Stream = OpenClipboard (PRIMARY_CLIP);
- ... /* check for Open errors */
- InitIFFasClip (iff);
-
- The argument to OpenClipboard() is the clipboard "unit" number and is
- normally PRIMARY_CLIP. Notice that neither of these calls specifies the
- I/O direction for the clipboard. This is because the same clipboard
- stream can be read or written many times. To read or write to a
- clipboard stream, use the BeginCB_Read()/EndCB_Read() or
- BeginCB_Write()/EndCB_Write() function pairs around the reads or writes
- you want to perform.
-
- 2b(iii). User Defined Streams
-
- The client programmer can define his own streams by creating read, write
- and seek functions for his stream and passing them to the library. The
- function for this is:
-
- InitIFF (iff, flags, stub, read, write, seek);
-
- where "stub," "read," "write" and "seek" are function pointers. Their
- format is described in more detail in the documentation for this
- function.
-
- The client defines the type of stream by setting the following bits in
- "flags":
-
- IFFM_MODE - this bit should have value IFFM_READ or
- IFFM_WRITE and indicates the I/O direction.
- IFFM_FSEEK - set this if the stream accepts forward seeks
- only.
- IFFM_RSEEK - set this if the stream accepts random access
- seeks (supercedes _FSEEK). Non-seeking
- streams will have neither bit set.
-
- 2c. OpenIFF() / CloseIFF()
-
- Once the IFF stream is ready to be read or written, the operation is
- initiated with OpenIFF() and ended with CloseIFF(). OpenIFF() simply
- prepares the IFF_File structure for a new I/O operation. CloseIFF()
- cleans up the IFF_File structure, freeing anything left over from the
- read or write operation. CloseIFF() can be used to end a read or write
- at any time.
-
- 2d. Summary
-
- In summary, there are three preparatory steps for using the IFF parsing
- library -- creating an IFF_File struct, initializing the stream and
- opening the IFF. After reading or writing the IFF file, the IFF_File
- structure used to do it must be closed and freed. The steps are
- summarized below (error checking has been removed for clarity -- KIDS:
- don't try this in your own code!):
-
- struct IFF_File *iff;
- /*
- * DOS_Stream and Clipboard_Stream are flags for the two
- * supported stream classes.
- */
-
- /* BEGIN */
-
- /*
- * Get an instance of an IFF_File struct.
- */
- iff = AllocIFF ();
-
- /*
- * Initialize the stream.
- */
- if (DOS_stream) {
- iff -> iff_Stream = Open (...);
- InitIFFasDOS (iff, IFFM_READ); /* or IFFM_WRITE */
- } else if (Clipboard_stream) {
- iff -> iff_Stream = OpenClipboard (PRIMARY_CLIP);
- InitIFFasClip (iff);
- }
-
- /*
- * Do stuff -- parse or write an IFF file.
- */
- OpenIFF (iff);
- if (Clipboard_stream) BeginCB_Read (iff); /* or BeginCB_Write() */
-
- DoStuff (iff);
-
- if (Clipboard_stream) EndCB_Read (iff); /* or EndCB_Write() */
- CloseIFF (iff);
-
- /*
- * Done with IFF -- close the stream.
- */
- if (DOS_stream) {
- Close (iff -> iff_Stream);
- } else if (Clipboard_stream) {
- CloseClipboard (iff -> iff_Stream);
- }
-
- /*
- * Free up IFF_File struct.
- */
- FreeIFF (iff);
-
- /* DONE */
-
- The steps between AllocIFF() and FreeIFF() can be done any number of
- times for the same IFF_File structure, and in the case of the clipboard,
- the steps between OpenClipboard() and CloseClipboard() can be done any
- number of times.
-
- What goes on in DoStuff()? Read on.
-
-
- 3. Reading
-
- Reading IFF files with the parsing library can be as simple or as
- complex as required. All IFFs are parsed by calling the single function
- ParseIFF(). The client specifies what function he wants ParseIFF() to
- perform by specifying how ParseIFF() should respond when it encounters
- certain types of chunks in the IFF file. This set of responses get
- stored in the IFF_File struct. For a virgin IFF_File struct, the
- ParseIFF() function will do nothing special, but it will still traverse
- the entire file and return an error code if encounters anything odd
- about the file it's traversing. If this is a valid and syntactically
- correct IFF file, ParseIFF() will return IFFERR_EOF meaning that it
- reached the end of the file.
-
- The parsing library has several predefined actions supported for dealing
- with chunks (more can be added with custom handlers). The parser can be
- directed to halt and return to the caller when it encounters a certain
- type of chunk, or it can squirrel the contents of the chunk away for
- future use. These two actions represent the general method for handling
- the two common types of chunks in IFF files -- data chunks and property
- chunks. The client can say which chunks (if any) should cause these
- actions with StopChunk() and PropChunk().
-
- 3a. StopChunk()
-
- StopChunk() allows the client to declare "stop" chunks, that is, chunks
- which cause the parser to return to the client when it enters them. Once
- StopChunk() is successfully called, the ParseIFF() function will return
- control to the client when the parser enters such a chunk. The chunk's
- contents can then be processed by the calling program and parsing
- resumed with ParseIFF() again. For example, suppose you wanted to read
- a color map out of an ILBM. This means that you would want to search
- through an IFF file looking for a CMAP chunk inside a FORM ILBM. You
- could use StopChunk() to do this:
-
- error = StopChunk (iff, ID_ILBM, ID_CMAP);
-
- If the return value, "error," is zero, then the specified IFF_File
- struct is now defined such that ParseIFF() will return when a CMAP chunk
- within an ILBM context is found. So the call,
-
- error = ParseIFF (iff, IFFPARSE_SCAN);
-
- will return zero if such a CMAP chunk is encountered, IFFERR_EOF if
- there is no such chunk, or an error code if there is something wrong
- with the IFF structure of the file. If it returns zero, then the stream
- associated with the IFF_File struct is positioned just at the beginning
- of the data in the CMAP chunk and can be read with one of the chunk reading
- functions, such as ReadChunkRecords(). So the whole sequence required
- to read a CMAP out of an open IFF file is just this:
-
- error = StopChunk (iff, ID_ILBM, ID_CMAP);
- if (error)
- fatal_error ("can't declare stop chunk", error);
-
- error = ParseIFF (iff, IFFPARSE_SCAN);
- if (error)
- fatal_error ("color map not found", error);
-
- ncolors = ReadChunkRecords (iff, &colormap, 3, 32);
- if (ncolors <= 0)
- fatal_error ("error reading color map", ncolors);
-
- printf ("found %d colors\n", ncolors);
-
- At this point, parsing could be resumed with another call to ParseIFF().
-
- 3b. PropChunk()
-
- Now suppose you want to do something more elaborate, like read an ILBM
- and display a picture. To do this, you need to read not only the CMAP
- chunk but also the BODY chunk which contains the picture data itself and
- the BMHD chunk which contains parameters essential to interpreting the
- BODY data.
-
- You could do all this with StopChunk(), just setting the parser to stop
- on all these types of chunks and processing each as you encounter it.
- This is what many IFF readers do today, but it will not always work.
- The problem, of course, is that there can be many BMHD or BODY chunks in
- a single IFF file and you need to actually parse the structure to know
- which header goes with which body. Fortunately, the IFF parsing library
- provides a simple way to do this through the PropChunk() handler.
-
- In the language of IFF, BMHD and CMAP chunks are properties of the BODY
- data chunk. (CMAP is really more like a data chunk, but this is one of
- the somewhat ambiguous areas of the IFF specification.) Like
- StopChunk(), PropChunk() lets the client program associate a certain
- action with a certain type of chunk. In this case, the contents of the
- chunk will be stored during parsing without the client programmer
- knowing about it. At any point in the parse, the client can search for
- the stored version of the chunk valid in this context with FindProp().
- The parser and PropChunk() take care of managing the lexical scoping
- rules for property chunks so that the data returned by FindProp() will
- always be valid for the given context.
-
- Although this may sound complex, the programmer interface is very
- simple. Here's example code to read a BODY chunk from an ILBM along
- with its associated properties.
-
- struct IFF_File *iff;
- struct StoredProperty *bmhd, *cmap;
-
- LONG ilbmprops[] = { ID_ILBM, ID_CMAP, ID_ILBM, ID_BMHD };
-
- /* IFF_File struct already allocated and initialized for read. */
-
- /*
- * Declare the properties to store.
- */
- error = PropChunks (iff, ilbmprops, 2);
-
- /*
- * Declare the chunk to stop on.
- */
- error = StopChunk (iff, ID_ILBM, ID_BODY);
-
- /*
- * Parse the file, searching for the BODY chunk and storing
- * the related properties on the way.
- */
- error = ParseIFF (iff, IFFPARSE_SCAN);
-
- if (error == IFFERR_EOF)
- { ... no BODY chunk ... };
- if (error != 0)
- { ... bad IFF file ... };
-
- /*
- * "error" was 0, so the parser is stopped at the start of the
- * BODY chunk, and the properties are stored. Recall the stored
- * properties with FindProp().
- */
- bmhd = FindProp (iff, ID_ILBM, ID_BMHD);
- cmap = FindProp (iff, ID_ILBM, ID_CMAP);
-
- /*
- * If either value returned is null, then the property chunk was
- * not encountered in the IFF file before the BODY chunk.
- */
- if (!bmhd || !cmap)
- { ... properties not found ... };
-
- /*
- * Pass the header info and color map to another routine that
- * will read the data from the current position in the IFF
- * stream and do something with it.
- */
- ProcessBody (iff, bmhd->sp_Data, cmap->sp_Data, cmap->sp_Size/3);
-
- After reading the body, the code could loop back to the ParseIFF() call
- and continue collecting BODY's. The CMAP and BMHD returned by
- FindProp() will always be the correct ones for each BODY chunk.
-
- 3c. Parse State
-
- Suppose you declare multiple stop chunks for a single IFF file. Once
- ParseIFF() returns with a zero value, you know that the parser has
- stopped at one of the chunks you wanted, but how do you tell which one?
- To determine this, the library provides methods for querying the current
- state of the parser. The functions CurrentChunk() and ParentChunk() get
- pointers to ContextNode structures which can be examined.
-
- CurrentChunk() returns a pointer to a context node for the chunk
- currently being parsed. For the example above, this is exactly the
- chunk you are interested in. Although much of the actual context node
- is private to the library there are a few public fields for finding
- information about the chunk. The cn_ID and cn_Type fields are the chunk
- identifier longword and the format type of the chunk, respectively, and
- cn_Size is the total size of this chunk in bytes.
-
- ParentChunk() takes a pointer to a context node and returns the pointer
- to the parent node, which corresponds to the chunk which encloses the
- given chunk. For a CMAP chunk in an ILBM for example, ParentChunk()
- will return the context node for the FORM ILBM chunk that contains the
- CMAP chunk.
-
- 3d. StopOnExit()
-
- The ILBM format is somewhat special in that it can be interpreted by
- only reading one data chunk (BODY) and its associated property chunks.
- Most IFF files, however, require that you read multiple data chunks
- before interpreting the meaning of the FORM. In order to facilitate
- this, the library provides the StopOnExit() call for pre-defining chunks
- that should cause the parser to return when they are exhausted and about
- to be popped. Calling this function for the FORM chunk itself provides
- a way for the client program to be notified when all the data chunks in
- a FORM have been read.
-
- When ParseIFF() returns when at the end of a chunk, it returns the value
- IFFERR_EOC rather than zero.
-
- The following code fragment demonstrates the use of StopOnExit() and how
- it can be used with StopChunk() to keep track of when the parser is
- traversing a particular chunk (in this case a FORM ILBM).
-
- struct IFF_File *iff;
- LONG error;
-
- /* "iff" allocated and initialized. */
-
- /*
- * Set up the IFF_File struct to stop when entering or leaving
- * FORM chunks of type ILBM.
- */
- StopChunk (iff, ID_ILBM, ID_FORM);
- StopOnExit (iff, ID_ILBM, ID_FORM);
-
- while (1) {
- /*
- * Advance the parser to the next stop point and exit at
- * end of file. Error code determines what we found.
- */
- error = ParseIFF (iff, IFFPARSE_SCAN);
- if (error == IFFERR_EOF) break;
-
- if (error == 0) {
- puts ("entering FORM ILBM.");
- continue;
- }
-
- if (error == IFFERR_EOC) {
- puts ("leaving FORM ILBM.");
- continue;
- }
-
- printf ("IFF error: %d\n", error);
- break;
- }
-
- 3e. ParseIFF() Control Values.
-
- The ParseIFF() function takes a control argument. This can have one of
- three values, and sets, essentially, how much control the calling
- program wants to exercise over the parsing process. The normal control
- value, IFFPARSE_SCAN, causes the function to scan the file and invoke
- the handlers for the chunks. In this control mode, ParseIFF() will only
- return on end of file, an error, or a stop chunk.
-
- The other two possible control values are IFFPARSE_STEP and _RAWSTEP.
- In both these modes, ParseIFF() will return every time a new chunk is
- entered (pushed) or just before an exhausted chunk is exited (popped).
- If the parser just entered a chunk, ParseIFF() returns zero, and if the
- parser is pausing at the end of a chunk, ParseIFF() returns IFFERR_EOC.
- In STEP mode, the handlers for chunks will still be called, while in
- RAWSTEP mode, the parser will return without calling any handlers.
-
- The single step modes are useful for using the IFF parsing library as
- the "back end" for a custom parser (examples are 'sift.c' and 'tree.c').
-
-
- 4. Writing Examples
-
- Writing IFF files is somewhat more straightforward than reading them,
- since you know in advance what you want to put in them and in what
- order. Initializing a stream for write depends on the stream: for
- DOS files, it just means opening the file with write access; for the
- clipboard it means calling BeginCB_Write(). For a custom stream type,
- set the IFFM_MODE bit in the iff_Flags word to IFFM_WRITE.
-
- Once the IFF file stream is ready to write, the client can write chunks
- and their data using PushChunk(), WriteChunkBytes() or
- WriteChunkRecords(), and PopChunk(). PushChunk() opens a new chunk and
- PopChunk() closes it and chunks can nest according to the rules of IFF
- files. Every IFF file is really a single chunk with all the data within
- as nested sub-chunks, so every IFF file is written with a single
- PushChunk() / PopChunk() pair surrounding all the writes.
-
- PushChunk() takes several arguments and returns an error code:
-
- LONG PushChunk (iff, type, id, size)
-
- The first argument is the IFF_File struct itself. The second is the
- format type code which is ignored for data chunks since they inherit
- their type from their parent chunk. The third is the chunk id code,
- which must be one of FORM, CAT or LIST for the first chunk pushed. Size
- is the size of this chunk in bytes. If this argument is
- IFF_SIZEUNKNOWN, the library will compute the size of the chunk from the
- data written into it. If it has some other value, the chunk will be
- forced to conform to the size given.
-
- As a writing example, a simple bitmap can be written as a FORM chunk of
- type ILBM with the following sub-chunks:
-
- BMHD - bitmap header data (always first).
- CMAP - color map data.
- BODY - interleaved bitmap data (always last).
-
- The following program might be used to write such a file:
-
- struct IFF_File *iff;
- struct BitmapHeader *header;
- LONG error, size;
-
- /* "iff" already allocated and stream initialized. */
-
- OpenIFF (iff);
-
- /*
- * Start the all-encompassing FORM chunk for the file.
- */
- error = PushChunk (iff, ID_ILBM, ID_FORM, IFF_SIZEUNKNOWN);
- if (error) ...fail;
-
- /*
- * Write the BMHD chunk. These are always the same size.
- */
- error = PushChunk (iff, 0, ID_BMHD, sizeof (*header));
- if (error) ...fail;
- size = WriteChunkBytes (iff, header, sizeof (*header));
- if (size != sizeof (*header)) ...fail;
- error = PopChunk (iff);
- if (error) ...fail;
-
- /*
- * Write the CMAP chunk. Three times the number of colors.
- */
- error = PushChunk (iff, 0, ID_CMAP, IFF_SIZEUNKNOWN);
- if (error) ...fail;
- size = WriteChunkRecords (iff, colors, 3, ncolor);
- if (size != ncolor) ...fail;
- error = PopChunk (iff);
- if (error) ...fail;
-
- /*
- * Write the BODY chunk. Calls WriteBODY() which would
- * presumably be a function that just calls WriteChunkBytes/Records
- * many times and then returns.
- */
- error = PushChunk (iff, 0, ID_BODY, IFF_SIZEUNKNOWN);
- if (error) ...fail;
- error = WriteBODY (iff, header);
- if (error) ...fail;
- error = PopChunk (iff);
- if (error) ...fail;
-
- /*
- * Close the outermost FORM chunk context. Doing this makes the
- * file effectively unwritable from this point.
- */
- error = PopChunk (iff);
- if (error) ...fail;
-
- CloseIFF (iff);
-
- You will notice that there are a lot of statements like:
-
- if (error) ...fail;
-
- How to deal with errors depends on how the code is written, but in
- general, once an error occurs, calling CloseIFF() on the offending file
- will shut things down cleanly. So the "...fail" expression could be a
- _goto_ passing control to the CloseIFF() call, or, if the writing calls
- are all kept in a subroutine, it could be a _return_ statement.
-
- Errors will occur not only for errors encountered writing to the stream,
- but also for IFF format errors. Programmers are encouraged to observe
- full error checking at all times, especially during initial testing
- since the library may signal IFF syntax errors that are part of the
- program itself. The writer is not guaranteed to catch all format
- errors, however, and programmers should always test their files with an
- IFF reader or scanner for the format they are writing.
-
- 5. Orthogonality
-
- In general, anything that works in read mode will work in write mode.
- The chunk context query functions, CurrentChunk() and ParentChunk(), as
- well as the value of the iff_Depth field, will give reasonable answers
- in write mode. Chunk handlers, however, do not work in write mode.
-
- By the same token, PushChunk() and PopChunk() will also work in read
- mode. Calling PushChunk() on a read mode file will force the parser to
- step into a new chunk at the given point in the file, and calling
- PopChunk() will force the parser to exit the current chunk, skipping
- past any data in the chunk that have not been read. Generally, however,
- you should not mix these low-level calls with calls to the more
- high-level ParseIFF().
-
- 6. Custom Handlers
-
- All the types of actions that can be performed when encountering or
- leaving a certain chunk, such as those initialized with PropChunk(),
- StopChunk() and StopOnExit(), are just built-in chunk handlers. If
- these actions are not complex enough, the client programmer can install
- his own custom handlers to perform any action he needs on entering or
- exiting a chunk. Chunk handlers are added to a given IFF_File struct
- with the EntryHandler() and ExitHandler() functions:
-
- error = EntryHandler (iff, type, id, func, stub, pos)
- error = ExitHandler (iff, type, id, func, stub, pos)
-
- "Type" and "id," as always, indicate what chunk to handle. "Func" is
- the client supplied handler function. This function will be called
- using the call-back stub, "stub." The "pos" argument indicates where
- the handler will be valid relative to the current context.
-
- 6a. User Call-backs Stub
-
- The library calls user code by putting arguments in registers, but since
- this is not convenient for programmers writing in a high-level language
- such as 'C', the library allows for different calling conventions with
- the call-back stub. The call-back stub for a language, GenericStub(),
- is a small assembly language routine that translates the register
- arguments to the calling convention of the host language. GenericStub()
- takes the four registers A0, A1, D0 and D1 and passes them to the actual
- function being called as if they were arguments. (The GenericStub() for
- small model Aztec C programs also restores the base register A4.)
-
- In the case of chunk handlers, A0 is a pointer to the IFF_File struct
- being parsed and A1 is a pointer to the ContextNode struct for this
- chunk. As result, user handler functions should receive the following
- four arguments:
-
- LONG
- MyHandler (iff, chp, D0, D1)
- struct IFF_File *iff;
- struct ContextNode *chp;
- LONG D0, D1;
- {
- ...
- }
-
- The D0 and D1 arguments have no meaning but are included since the stub
- function will provide them.
-
- 6b. Handler Scoping
-
- The "pos" argument to the handler installation functions indicates the
- handler's scope -- where it's valid. There are three possible scopes:
- ROOT, TOP and PROP.
-
- A pos of IFFSLI_ROOT will install the handler into the global context
- which makes it valid throughout parsing. Any time a chunk with the
- given type and id is encountered, a handler in the global, or default,
- context will be called.
-
- A pos of IFFSLI_TOP will install the handler into the current context,
- meaning that it will only be valid as long as the current chunk is being
- parsed. It will also override any handlers installed at a lower level,
- such as one for a chunk containing this chunk, or a handler installed in
- the default context. Once the current chunk ends, the handler will be
- purged.
-
- IFFSLI_PROP scoping is like the TOP option except that the handler is
- valid for the nearest containing FORM or LIST chunk. This corresponds
- to the scoping for shared properties.
-
- The ability to override other handlers is useful for scanning nested
- formats. For example, the client might install default handlers for
- ILBM chunks to be used for interpreting a bitmap format. But if the
- parser encounters a FORM ANIM, the client might want to install
- different handlers for ILBM chunks for the course of parsing the ANIM.
- By installing handlers with IFFSLI_TOP position when first entering the
- FORM chunk for the ANIM, they will only be valid while the ANIM is being
- scanned and will automatically go away when that FORM chunk ends.
-
- All the built-in handler installers, such as PropChunk() and
- StopChunk(), use TOP position for their handlers. This is the same as
- ROOT position if no chunk is currently being parsed, like just before or
- after an OpenIFF() call.
-
- 6c. Handler Return Value
-
- Chunk handlers return a value in D0 (or are defined as LONG functions in
- 'C'). This is their error return code. Returning zero allows the
- parsing to continue normally, and any other value will cause ParseIFF()
- to return that value to the client as its returned error code. The
- exception is the special code, IFF_RETURN2CLIENT, which will signal
- ParseIFF() to return to the caller but to return zero.
-
- If a custom chunk handler gets an error from an IFF library function, it
- should normally pass that error code on in D0. A handler can in general
- return any value, but negative codes are reserved for library internal
- errors. Unless the handler is using its return value to signal
- something special to the calling program, or if it caught an error, it
- should return zero.
-
- 6d. Summary
-
- The client program can arrange for any function to be called when the
- parser enters or exits any chunk. These functions, called "handlers,"
- should either accept their arguments in registers, or be set up to get
- their arguments translated by a stub function. The provided stub
- function for each language, GenericStub(), takes the arguments A0, A1,
- D0, D1 and passes them in that order to the user's function. Handlers
- get the two arguments:
-
- A0 -- pointer to the IFF_File struct being parsed.
- A1 -- pointer to the ContextNode struct for this chunk.
-
- Handlers are installed with the ExitHandler() and EntryHandler()
- functions, which specify a stub function, if any, and a context position
- argument. Entry handlers get called just after their chunk is pushed,
- and exit handlers get called just before their chunk is popped.
-
-
- 7. Internal Design - The Context Stack
-
- At the heart of the IFF_File structure lies the Context Stack. This is
- a LIFO stack of ContextNode structures, each one corresponding to a
- chunk in the current parse state. As the parser (or writer) traverses
- the file, it pushes and pops context nodes on and off the context stack
- to maintain an internal model of the file structure. Arbitrary data can
- be associated with a context node and will be handled appropriately to
- its scoping context.
-
- Each ContextNode element on the stack has fields for chunk ID, type,
- size, and a count of the number of bytes already read from this chunk.
- As an example of how this stack structure gets used to read IFF files,
- consider the simple image file (which might have been written by the
- writing example):
-
- "FORM" {
- "ILBM"
- "BMHD" {20 bytes}
- "CMAP" {21 bytes}
- "BODY" {32000 bytes}
- }
-
- When ParseIFF() is first called, the first chunk from the file is pushed
- onto the stack (remember - the entire file is one chunk). So at this
- step, the stack consists of one element:
-
- +----------+
- |FORM 32070|
- |ILBM 4 |
- +----------+
-
- This means that the current chunk being parsed has ID `FORM', type
- `ILBM' and contains 32070 bytes of which 4 bytes have been read (the
- type identifier itself). Because FORM chunks are grouping chunks in the
- IFF format, the parser will push a new chunk element on the context
- stack, in this case, the BMHD chunk.
-
- +----------+ +----------+
- |BMHD 20 | |FORM 32070|
- |ILBM 0 | |ILBM 12 |
- +----------+ +----------+
-
- At this point, the file is positioned ready to read the first data byte
- in the BMHD chunk, and the context stack correctly reflects this by
- showing that the top chunk is the BMHD chunk and zero bytes have been
- read. The bytes read count of the parent FORM chunk has increased to
- 12, since the 8 bytes required to push the BMHD chunk on the stack get
- counted against the total in the FORM. The application program or a
- handler could then read out the BMHD chunk's contents, whichever is more
- appropriate. In either case, the chunk reading routines will update the
- byte count to reflect the number of bytes read.
-
- +----------+ +----------+
- |BMHD 20 | |FORM 32070|
- |ILBM 20 | |ILBM 12 |
- +----------+ +----------+
-
- When the chunk is completely processed, any bytes unread will be skipped
- (optional pad bytes will be skipped at this point), the chunk will be
- popped off the stack, and it's parent context element will be updated to
- indicate how many bytes have been read out of it at this stage. The top
- context element will always have a correct scan count, even though lower
- elements may be out of step -- they will be caught up when they become
- the top element. After the BMHD chunk has been read, the context stack
- looks like this:
-
- +----------+
- |FORM 32070|
- |ILBM 32 |
- +----------+
-
- Reading the CMAP and BODY chunks go the same way ...
-
- +----------+ +----------+
- |CMAP 21 | |FORM 32070|
- |ILBM 0 | |ILBM 40 |
- +----------+ +----------+
-
- +----------+ +----------+
- |BODY 32000| |FORM 32070|
- |ILBM 0 | |ILBM 70 |
- +----------+ +----------+
-
- After everything is read, the top context element looks like this:
-
- +----------+
- |FORM 32070|
- |ILBM 32070|
- +----------+
-
- Once this main context is popped, the ParseIFF() function will return
- IFFERR_EOF.
-
- 7a. Local Context Items
-
- Each context node also has a list of associated items, called Local
- Context Items. These items can be anything, and are identified by an
- ident code as well as an ID and type code just like chunks. Each local
- context item also has its own purge function vector, so that the item
- can be disposed of by the library when it pops its context node.
-
- LocalContextItems are allocated by the client programmer with the
- AllocLocalItem function.
-
- item = AllocContextItem (ident, type, id, usize)
-
- The "ident" argument is the major class type of item being created.
- This is just a longword identifier like chunk type and id codes, and is
- created the same way (but out of lower-case letters to be distinct from
- chunk format codes). There are a few predefied local context class
- ident codes that are used internally:
-
- "enhd" -- entry handler
- "exhd" -- exit handler
- "prop" -- stored property
- "coll" -- collection list
-
- It's clear from these examples that many of the basic capabilities of
- the parser are implemented as special classes of local context items.
- In fact the context stack and local context items underlie most of what
- the parser does.
-
- The "type" and "id" arguments are two more identifying longword codes
- for the item being allocated. Often these are the analogous to the type
- and id for chunks, but they don't have to be. The "usize" argument is
- the number of bytes of use data space to allocate along with this local
- context item. To get access to this block of user data space, use the
- LocalItemData function:
-
- data = LocalItemData (item)
-
- The function returns a pointer to the user data associated with the
- local context item "item". This is the only supported way to get a
- handle on this data.
-
- Local context items can be attached to any context node in the context
- stack, as well as to the "default" context associated with an IFF_File
- structure. This can be done with the StoreLocalItem or
- StoreItemInContext functions:
-
- error = StoreLocalItem (iff, item, pos)
-
- StoreItemInContext (iff, item, cnode)
-
- StoreLocalItem stores an item relative to the current parse context
- according to the "pos" argument which can have values of IFFSLI_TOP, the
- "top" or current context, IFFSLI_ROOT, the "root" or default context, or
- IFFSLI_PROP, the property scope closest to the current context.
- StoreItemInContext attaches an item directly to a context node given a
- pointer to it.
-
- 7b. Local Context Item Purge Vectors.
-
- When a Context Node is popped off the context stack (i.e. when its chunk
- is exited) the local items associated with it get purged. Normally, the
- library just calls FreeLocalItem(item) to deallocate items, but since
- there can be any user data also stored in the local item, the library
- also provides a mechanism for the user to do his own clean up of items
- to be purged. The user cna set his own purge vectors with:
-
- SetLocalItemPurge (item, stub, purge)
-
- The purge vector will be called (via the stub, if any) with the
- following arguments:
-
- A0 -- pointer to the IFF_File struct.
- A1 -- pointer to the LocalContextItem struct to be deleted.
-
- The user should do his own clean up and then call FreeLocalItem on the
- item to free it and the user data block.
-
- Items that have been stored in the default (or root) context will not be
- purged until the IFF_File structure itself is disposed of with FreeIFF.
-
- 7c. Finding Local Context Items.
-
- Once items have been stored in the context stack, they can be retrieved
- using the FindLocalItem function. The way that FindLocalItem goes about
- locating the requested item allows for the transparent handling of the
- IFF scoping rules.
-
- item = FindLocalItem (iff, ident, type, id)
-
- Given a set of ident, type and id codes to look for, this function first
- looks at the current context level in the context stack for an item that
- matches the given codes. If none is found, it will look in the next
- deeper context, and then the next deeper and so on. If it cannot find
- any in the stack itself it will then search the default item list. It
- will return the first item it finds that matches the three code values.
- If it can find none, it will return NULL.
-
- 7d. Stored Properties: An Example of Local Context Items.
-
- The behavior of FindLocalItem makes dealing with stored properties
- almost trivial, so by way of illustration, the operation of how
- properties are stored will be explained with a detailed example.
-
- What happens internally is that as the reader runs into recognized
- property chunks, it stores them away as local context items. When the
- user wants them, the library uses FindLocalItem to find the instance of
- stored property that is closest to the top of the stack and returns that
- to the user. As context stack elements are popped, invalid stored
- property chunks are deleted and older chunks which are now valid come to
- the front again. As a specific example, take this complex IFF file:
-
- "LIST" {
- "JUNK"
- "PROP" {
- "ILBM"
- "BMHD" {...}
- "CMAP" {...}
- }
- "FORM" {
- "ILBM"
- "BODY" {...}
- }
- "FORM" {
- "ILBM"
- "CMAP" {...}
- "BODY" {...}
- }
- }
-
- It is a LIST containing two ILBM FORM chunks with a PROP chunk declaring
- default BMHD and CMAP chunks for ILBM FORMs. Since the user is
- interested in interpreting the ILBM BODY chunks with BMHD and CMAP
- chunks as properties, he will declare them as properties with the
- PropChunk function. What this function does is install an entry handler
- for ILBM BMHD and ILBM CMAP chunks which will store them as local
- context items in a property scope.
-
- The parsing sequence for this file starts with the context stack being
- initialized to the overall chunk for the file:
-
- +----------+
- |LIST 64144|
- |JUNK 4 |
- +----------+
-
- The parser then pushes elements for the PROP and its internal BMHD
- chunk.
-
- +----------+ +----------+ +----------+
- |BMHD 20 | |PROP 62 | |LIST 64144|
- |ILBM 0 | |ILBM 12 | |JUNK 12 |
- +----------+ +----------+ +----------+
-
- Now the parser invokes the handler for this ILBM.BMHD chunk and the
- handler reads it itself, storing the contents of the chunk in a buffer.
- It then attaches this buffer to a local context item and stores it in
- the PROP context for this chunk. This will be the LIST element in the
- context stack, since this chunk represents the context in which this
- property is valid. Once the reader exits the LIST chunk, the property
- will no longer be valid and will be automatically expunged. The local
- item will have an ident of "prop" and type and id of ILBM and BMHD.
-
- +----------+ +----------+ +----------+
- |BMHD 20 | |PROP 62 | |LIST 64144|
- |ILBM 20 | |ILBM 12 | |JUNK 12 |
- +----------+ +----------+ +----------+
- |
- prop ILBM.BMHD
-
- The parser processes the CMAP chunk the same way and pops the PROP
- element since it's exhausted. The remaining LIST context element has
- property chunks hanging off of it stored as local context items as it
- begins to parse the next chunk.
-
- +----------+
- |LIST 64144|
- |JUNK 74 |
- +----------+
- |
- prop ILBM.BMHD
- prop ILBM.CMAP
-
- When the parser reaches the BODY chunk, the stack looks like this:
-
- +----------+ +----------+ +----------+
- |BODY 32000| |FORM 32012| |LIST 64144|
- |ILBM 0 | |ILBM 12 | |JUNK 82 |
- +----------+ +----------+ +----------+
- |
- prop ILBM.BMHD
- prop ILBM.CMAP
-
- Since the user needs the stored properties to correctly process the BODY
- chunk, he will recall the stored properties with FindProp. This
- function just calls FindLocalItem for "prop" ident items with the
- requested type and id which finds the stored property chunks attached to
- the LIST context node. This is the correct behavior here since this
- FORM has no overriding properties. Parsing the next ILBM FORM is
- different, however. As the parser enters this FORM, it invoke the
- property handler for the CMAP chunk.
-
- +----------+ +----------+ +----------+
- |CMAP 21 | |FORM 32042| |LIST 64144|
- |ILBM 0 | |ILBM 12 | |JUNK 32102|
- +----------+ +----------+ +----------+
- |
- prop ILBM.BMHD
- prop ILBM.CMAP
-
- The handler reads the contents of the CMAP and attaches it to the
- context element for the FORM, since that is the scope for this property.
- Now, when the reader encounters the BODY chunk and passes control to the
- user to deal with it, the user sees the correct properties for the given
- context when using FindProp().
-
- +----------+ +----------+ +----------+
- |BODY 32000| |FORM 32042| |LIST 64144|
- |ILBM 0 | |ILBM 34 | |JUNK 32102|
- +----------+ +----------+ +----------+
- | |
- prop ILBM.CMAP prop ILBM.BMHD
- prop ILBM.CMAP
-
- When the application asks for a CMAP, it gets the overriding one -- the
- one attached to the FORM element. When it asks for a BMHD, it gets the
- default one specified for this LIST. When the context nodes get popped
- as result of continuing to parse, or of the user program calling
- CloseIFF, the stored properties will be purged as if they were never
- even there.
-