PART 1 HOW TO WRITE A VIEWSTORE UTILITY by Mark Colton, author of VIEW, VIEWSHEET AND VIEWSTORE Since the available space for ROM code is limited to 16k, ViewStore has provision for extra programs or utilities that exist outside the ROM to be used. ViewStore is supplied with several utilities that supplement the ROM, and has an interface built into it to allow utility programs to use routines inside the ROM. Certain areas of memory are also allocated for use by utility programs. Given the knowledge of the interface and memory allocation, it is possible to write extra utilities for ViewStore. The purpose of this document is to define the utility interface to enable third parties to write their own utilities. A good knowledge of ViewStore and assembly language will be essential. The Interface Various routines within the ROM are available for use by utilities. The routines provide the utility with the means to access format files and data records and indexes and assorted useful functions. The ROM has a jump table at the beginning which directs calls to the various routines in the ROM. As well as access to the ROM routines, the utility has memory allocated to it. Three areas of memory are available to a utility: A section of zero page A section of language absolute workspace A section of main memory The amount of zero page and language workspace available to a utility depend upon which ROM routines the utility is going to use. The size and position of the piece of main memory available can only be determined when the utility is run: pointers to the start and end of the main workspace are passed when the utility is started. The zero page, language workspace and main memory not available to the utility is used by the ViewStore ROM itself. A utility should not alter this memory, but the addresses of some locations are defined to allow utilities to read useful parameters. Utility Format The format of a utility must conform to certain rules. When a utility is located and loaded from the filing system, ViewStore relocates the code to run at a particular point in memory. This point varies according to the size of the format file loaded and the MOS "high water mark". On machines with second processors attached, ViewStore relocates the utility to run in the space left above the ROM: from &C000 to &F800. Using this relocating system, ViewStore make optimum use of the memory available. Since it is not easy to write position independent code for the 6502, the ROM includes a relocating system. The utility must provide certain information about itself to enable this system to work. The format of a utility is as follows:- .start JMP code EQUW bitmap-start EQUW start EQUS "version no/copyright string" .code /utility code begins here /end of utility code .bitmap /relocation bitmap /end of utility The first word after the initial JMP gives the offset from the beginning of the utility to the relocation bitmap. Since the utility is relocated after being loaded, the actual assembly address is not important, but ViewStore must be told at what address the utility has been assembled, so that it can calculate how much needs to be added or subtracted from the addresses to be relocated. The second word after the JMP gives the assembly address. This &2000 for most of the supplied utilities. The version no/copyright string is not essential, but it's a good idea to include one so that you can identify the code. The main hurdle for anyone writing their own utilities will be the generation of the relocation bitmap. This identifies the addresses that must be relocated. There is a bit in the bitmap for every byte of code in the utility, excluding the bitmap itself. A bit set to zero indicates that an address is not to be relocated; a bit set to one indicates that an address is to be relocated. It is only possible to relocate addresses using this system, not single bytes. This means that it is not possible to set up or move addresses using immediate data: LDA #place STA var LDA #place AND &FF STA var+1 is not allowed. You must use the form: LDA plcew STA var LDA placew+1 STA var+1 .placed EQUW place In the bitmap, a bit which is set is taken to refer to a 16 bit address within the program; it is therefore impossible to have two adjacent bits set since the second bit it referring to the high byte of the address. Given eight adjacent bytes, all represented by a single byte in the bitmap, the most significant bit in the bitmap byte corresponds to the first code byte, and the least significant bit to the last code byte. As an example: the first byte of a relocation bitmap always corresponds to the JMP and EQUW structure at the beginning of the utility. Assuming that you have a copyright string (which contains no addresses to be relocated), the first byte of the bitmap should always be &44. The bit string for this is: MSB LSB 0 1 0 0 0 1 0 0 The first three bits correspond to the three bytes of the JMP instruction. The second two bytes of the JMP instruction contain the address to jump to, which must be relocated. The first bit of the two bits for this address is therefore set. The next two bits correspond to the bitmap offset word. This remains constant for any load address, so these two bits are zero. The next two bits are for the assembly address, which will alter as the utility is relocated, and the first bit is a 1 accordingly. The last bit is for the first byte of the copyright string; zero since this has no addresses to be relocated. The bitmaps for the utilities which are supplied with ViewStore were generated automatically by an assembler which is not available on the market. If you are going to write a utility for ViewStore, you must find a way of generating relocation bitmaps. This could be done in one of three ways: 1. Generate it by hand 2. Write a program to take an assembled utility and generate the bitmap 3. Modify an assembler to generate relocation bitmaps 4. Assemble it at two different addresses and write a program to compare the two resulting code files. Those locations which have changed need a set bit in the bitmap. For most people, the last option will be the simplest. The Utility Environment Most utilities will want to operate upon existing databases. It is possible, though, to have a utility which does not operate on existing data, but creates data, or doesn't act on data at all. An example of this is the SETUP utility, which creates blank databases. It doesn't refer to any existing data. Utilities which need to access either format files or data files must first check that a database has been loaded, and abort with an error message if there is no loaded database. This is done by checking the location FILMOD. If FILMOD is non-zero, then a database is loaded. If zero, there is no database loaded. The normal sequence of operation will be to load a database in ViewStore, with the LOAD command. This loads the format file into memory, and locates the data file. A utility is then started with the UTILITY command. The utility can read the format file as it requires, and can open and process the information in the data file. It can use a subset of data identified by the select file. Once the utility is running, it can take control of the machine as it needs to, using the memory available to it, and the ROM routines as required. Naming of Addresses In this document, all location and routine addresses will be referred to by name. Tables of addresses and values are at the end of the document. You will notice that the names of a block of addresses being with "TEMP". These locations are available for use by the utility as temporary storage, but some are used and altered by routines in the ROM. Temporaries The numbered temporaries are all either a single byte or two bytes long. Those from TEMPFD to TEMP05 are all one byte long; those from TEMP06 to TEMP14 are all two bytes long. The two byte temporaries are used to store and pass addresses; the single byte temporaries are used to store one byte quantities. Often, values are passed to and from routines using temporaries, as well as registers in the CPU. Many routines "corrupt" certain temporaries; a list of the temporaries a routine corrupts is given in a summary at the end. Utilities can use temporaries whenever they wish, but of course their use must not clash with any routines that you call in the ROM. Entry Parameters When the utility is started, the following data is provided: TEMP14 contains the start address of free main memory VWSLIM contains the address of the byte after the last free byte in main memory VWSLIM will not change while the utility is running, and the utility must not alter VWSLIM. TEMP14 may be altered by a ROM call, so it is best to store it somewhere else for later reference. The utility is called with a JSR instruction, and ViewStore expects the utility to hand back control, when it is finished, with an RTS instruction. .start TSX STX stksav . . .error LDX stksav TXS RTS Zero Page Zero page is divided up into four areas of different types: ViewStore variables read only for utilities. Below &50. Temporaries TEMPFD-TEMP Floating point FACCxxand FWRKxx. Start at &6B. read/write for accumulators utilities; if the floating point calls are not used by the utility. this area can be used as general workspace. General workspace VWSXTZ-&8F inclusive. Language Workspace LWORK 16 byte parameter block used by ROM routines. FBLOCK 27 byte filename work area. LINBUF 256 byte work area used by one or two ROM routines. VWSXTL-VWSITL area used by ISAM index system. Can be used as general workspace if the utility is not using indexes. General workspace VWSITL-&7FF inclusive. ROM Routines I will describe the ROM routines in the categories that they fall into. The routine addresses and parameters are summarised in table 1. All routines should be called with a JSR instruction, except for CALUTI which should be called with a JMP instruction, since returning control to the utility is not usually sensible as the new utility will overwrite the old one in memory. Data File Control These routines give the utility access to the database data file, using the current select file if required. It is only possible to read sequentially through the data file using these calls, but the data will be returned in sorted order if the selected data was sorted. The utility should not close the intermediate file if it uses it. This is done automatically when control is returned to the ROM. INIIMF Initialise data sequence. Called to start the data reading sequence. MXTIMF Get next from data sequence. Each call returns the next data record in the sequence. INIIMF This routine is called to initiate a sequence of data transfers. It opens the main data file, and stores its handle in the location EFILE. According to the state of the carry flag on entry, it asks the user if he wishes to use a select file. The user responds with a yes or no, and ViewStore opens the select file (S.database) accordingly. After this, the select file is transparent to the utility; repeated calls to NXTIMF will either return all the records in the data file if the select file is not being used, or the subset of records in the select file, if specified. On entry: CC Don't ask "Use select file (Y,N)?" question. CS Ask select file question. On exit: VC No error. VS Error; error code in A. NXTIME After starting the sequence with a call to INIIMF, repeated calls to MXTIMF return the records in the data file one by one. On entry: A low byte of address to store record. Y high byte of address to store record. TEMP13 address of the byte after the last byte available to store the record. On exit: VC No error. VS Error; error code in A. CC Not end of file. CS End of file (returned on the call after the last record has been processed). Errors Many of the ROM routines can return an error status. An error is usually indicated by either the Carry flag (C), or the Overflow flag (V). When an error is indicated, the error code is in the A register. To report the error to the user, call the routine REPERL with this code A. The various error codes and messages are summarised later. A utility can use a ROM error message by loading the appropriate code into A, and calling REPERL. All file calls have the same error trapping system: after a call, V is set to indicate an error, clear if there was no error. This includes errors causing a BRK, that is control is returned to the calling routine even when a BRK is occurred. When you call REPERL with the returned error code, the BRK message will be reported as normal. REPERL Reports the error message for the error code in A: On entry: A contains error code. On exit: A,X,Y undefined. Field and Record Control Much of ViewStore's manipulation is on fields and records; accordingly, there are several routines available to make this easier. There are some routines to locate fields in the header, format file or current record; routines to compare field values; and routines to find the size of a given field. Most of these routines use the two temporaries TEMP06 and TEMP07. TEMP06 points to either a field within the format file, or a field within the current record. TEMP07 points to a field within the current record. The Y indirect indexed addressing mode is used in conjunction with these temporaries to access the field contents: the temporary points to the beginning of the field, and the Y register gives the offset from there. Remember that the format file itself is in the same format as a data file. The same routines are used to process information in records of the database as in the format file itself. For each field in the database, there is a record in the format file, and this record details the characteristics of its corresponding field in the database. The header record is the first record in the format file. Data Format The data format is summarised in table 10 at the end of the document. All fields in ViewStore are stored in ASCII, even numbers and dates; each field ends with an end of field marker; each record ends with an end of record marker; and the file ends with an end of file marker; after the end of file marker, the file is padded up to the physical end of file with null characters. If you are processing a field's contents, you should test for the end of field using the CHKEOF and CHKEOR routines. These routines set the flags according to the character that they find. Don't check for the character value explicitly. Generally, when you make a call to a routine that locates a field, the x register indicates where the field is to be found: X=0 Field in header; A has field number. X=1 to 254 Field in format file; X gives format file record number; A has field number. The field numbers of the various format file fields are summarised below. X=225 Field in current record; A has field number; Y has record number. Fields within a record are numbered from 1 to 254. If you ask for a field which is not in the record, the routine will return with the Carry flag set. Whereas ViewStore knows where the format file is located, the address of the current record could be anywhere, and before fields within the current record can be accessed, you must tell ViewStore its address with the SETDPS routine. GETFLD General field locate routine; can locate a field in the header, format file or current record. GETFRC Find the address of a field in the current record. GETXFL Return first non-space character of a field in the A register, folded to upper case if applicable. SETDPS Set the address of the beginning of the current record. CHKEOF Check the character in the A register for an end of field character. CHKEOR Check the character in the A register for an end of record character. SIZFLD Return the size of a given field. CMPFLD Compare two fields of the same type and set the flags. SCHFLD Return the number of a field, given its name. SCHFLN Return the next field number, given a name, for an ambiguous name specification. GETWID Return the display width for a particular field. GETKYW Return the key width for a particular field. CALSBN Calculate the number of spaces required to be output before a numeric field to right justify it within the display width. GETFLD This routine locates a field in either the database header, the format file or the current record. If the field is in the current record, the address of the first character of the field is set into TEMP06 and also into TEMP07. If the field is in the format file, TEMP07 is left unaltered, and the address of the field is put into TEMP06. If X is equal to 255, then the routine uses the value in the Y register to locate a record in a list of current records. The list of records is numbered from 0 onwards. The usual way to use this part of the routine will be with Y set to zero, in order to locate a field within a single current record. If you are using a list of records, then you must not set Y to too high a value, so that the routine runs off the end of the list, unless you have an end of file marker after the last record. Before you use this routine, you must have set the position of the first record in the list by using the SETDPS call. On entry: A field number of field to locate; field start at 1. X=0 find field in header record. X=1 to find field in format file record; X X=254 gives the number of the format file record. X=255 find field in list of current records; Y has the number of the record to search, starting at zero. Y only significant if X=255. On exit: CS field or record not found; TEMP06 (and TEMP07 if X=255) point to the end of record marker if field not found, the end of file marker if record not found. CC field and record found; TEMP06 points to the beginning of the field; if the call was made with X=255 then TEMP07 also points to the beginning of the field. A,Y undefined. X preserved. GETFRC This call first sets X to 255, and then calls the GETFLD routine. The entry and exit conditions are as for GETFLD when X-255, except that X will always return set to 255. GETXFL GETXFL returns the first non-space character in a field, folded to upper case if alphabetic. It is intended primarily for reading the value of single character fields in the format file, such as the "Field type" field. GETXFL first calls the routine GETFLD. The entry conditions are the same as GETFLD. If the call to GETFLD fails, ie the Carry flag is set, then the A register is cleared, and the routine ends. If the field is found, then the first character of the field is returned, folded to upper case if alphabetic. If the field is blank, then the end of field marker will be returned. On entry: See GETFLD On exit: TEMP06 and TEMP07 set as for GETFLD. CS field or record not found, as GETFLD; A set to zero. CC field found; A contains first non-space character, folded to upper case if alphabetic. X,Y See GETFLD. SETDPS This routine stores the address of the records to be used when using one of the field locate routines with X set to 255. It should be called whenever the address of one of the records in the list or of one of the fields in the records has altered. It need not be called if none of the fields has moved, since ViewStore will keep track of its position in the list of records, and move backwards or forwards as necessary to find the field you have asked for. If you are reading records one by one using the NXTIMF call, for example, then you must call SETDPS with their address of the record for each record that you read: the alignment of the fields will alter for each record. On entry: A contains low byte of the address of the first record in the list. Y contains the high byte of the address of the first record in the list. On exit: A,X,Y undefined. CHKEOF CHKEOF checks the character in the A register for an end of field marker. It should be used rather than checking for the character explicitly since it handles classes of characters rather than single values. Generally, it is not necessary to detect illegal characters explicitly, it is enough to detect them as an end of field marker. On entry: A contains character value to be checked. On exit: EQ end of field. CS end of record (EQ also set). VS illegal character (EQ also set). A,X,Y preserved. CHKEOR CHKEOR checks the character in the A register for an end of record marker. On entry: A contains character to be checked. On exit: EQ end of record. CS end of file (EQ also set). VS space character (EQ also set). A,X,Y preserved. SIZFLD SIZFLD is provided to allow you to determine the size of a field. First of all you should locate the field, using one of the field locator routines such GETFLD, which set up TEMP06. Then call SIZFLD. On entry: TEMP06 points to the beginning of the field. On exit: A,Y have length of field in characters. EQ zero length field. X preserved. TEMP06 preserved. CMPFLD CMPFLD compares the values of two fields of the same type, and sets the 6502 flags register like the CMP instruction. TEMP07 points to the first field (equivalent to the contents of the 6502 A register in the CMP instruction), and TEMP06 points to the second field. If the two fields being compared are strings, then wildcards are allowed in the second string. On entry: A contains the field type: A, N, D or Y; must be in upper case. TEMP07 points to field 1. TEMP06 points to field 2. On exit: VS error in one of the fields supplied: eg illegal date; result not valid. C flag set according to compare. Z flag set according to compare. A,X,Y undefined. SCHFLD SCHFLD is used to find the number of a field, given its name. It searches the list of fields in the format file until it finds one that fits the name given. The name that you specify can obtain wild cards: the single wildcard "?", and the multiple wildcard "*" are both allowed. SCHFLD will always return the first field in the format file that fits the name you have given. You can either continue searching for more fields by using the SCHFLD call described next. The name of the field is set up in the 16 byte LWORK area. It should be terminated by a null, or an end of field marker. It must not be longer then 16 bytes, including the delimiter. On entry: LWORK contains name to search for; maximum of 16 bytes including delimiter; delimiter null or end of field marker; wildcards "?" and "*" valid. On exit: CS no field found to match the name; A has error code. CC field found OK; field number in X. SCHFLN After calling SCHFLD, you can search for other fields which also fit the field specification that you gave given, by making repeated calls to SCHFLN. Before you call SCHFLN, you must have called SCHFLD first, to start the sequence, and this call must have successfully found a field. You can keep calling SCHFLN until the call returns with the Carry flag set to indicate that it has found no more fields. On entry: Must have called SCHFLD first, and this must have returned with the Carry flag clear. A Contains field number to start searching from. This should be one more than the last value that SCHFLD or SCHFLN returned in X. On exit: CS no more fields found; A has error code (report only if required). CC field found; field number in X. GETWID GETWID returns the display width of a field, as defined in the format file. If there is no display width defined, a series of defaults comes into action. Display width defined Display width No display width, Sheet mode 18 No display width, Card mode 0 On entry: X contains number of field for which width is required. On exit: CS field doesn't exit. CC field found; A has display width. X preserved. Y undefined. GETKYW This routine finds the key width for a given field. It uses the value defined in the format file, if any; otherwise a system of defaults operates: Key width defined Key width No key width, display width defined Display width No key width or display width 10 On entry: X contains number of field for which width is required. On exit: CS field doesn't exist. CC field found; A has already width. X preserved. Y undefined. GETKYW This routine finds the key width for a given field. It uses the value defined in the format file, if any; otherwise a system of defaults operates: Key width defined Key width No key width, display width defined Display width No key width or display width 10 On entry: X contains the field number for which the key width is required. On exit: A contains key width; if field doesn't exist, a default key width of 10 is returned. X preserved. Y undefined. CALSBN This routine is used when displaying numeric fields, to calculate the number of spaces to be output before the number in order to right justify the number within its field width. This also takes into account the decimal places specified in the format file. On entry: X contains the field number. TEMP07 points to the beginning of the field in question. TEMP03 contains the field display width, as returned by the GETWID routine. On exit: A gives number of spaces to output, zero if the number is wider than the field, or there are too many decimal places. X undefined. Y undefined. TEMP03 preserved. TEMP07 preserved. File Control File handling in ViewStore is centred around three things: error handling, FBLOCK and prefixes. Since the error handling provided by the normal filing system interface provided is completely unsatisfactory for a program such as ViewStore, I have developed a system which gives control of what happens after a disc or filing system error. For the utility writer, this system is transparent: you can forget about it as long as you use the calls provided, and don't call the filing system directly. If you do this, you can forget all about BRK errors and handlers. The system is the same for all file calls: the state of the Overflow (V) flag indicates after a call whether an error has occurred. If there has been an error, then the V flag is set, and the A register contains the error code. If you detect an error, you should unravel yourself from any routines, report the error by calling the REPERL routine, and then close any files that you have opened yourself, before returning to control of the ROM. You shouldn't close the intermediate file: this is done automatically when control is passed back to the ROM. Filenames FBLOCK is a small of memory used to store and manipulate filenames. Several routines are provided which work on the filename in FBLOCK, altering directories and prefixes. A filename in ViewStore is made up of three parts: Prefix Directory Name ViewStore maintains a list of the current prefixes for each different file type: data; format; sort and so on. A routine which adds a specified prefix to a directory and name stored in FBLOCK is available. The maximum length of a prefix is 13 characters, excluding delimiter. The current prefixes can only be altered with the PREFIX command in ViewStore's Command Mode. ViewStore considers directories to be single character; of course the prefix can include multiple character directories, but the filenames of data and format files, for example, begin with directories, and these are always single character directory names, whatever the filing system. Directories must be separated from the name itself by a dot, making the total size of the directory section of a filename 2 characters. The name part of the filename can be up to 10 characters long. This does not include the directory and separator, or the delimiter. Names are always delimited with a Carriage Return character. Part Max. Size (exc. delimiter) Example Prefix 13 :2. Director 2 d. Name 10 datafile MOVFBK Moves a filename from the store area into FBLOCK. MOVNAY Moves a filename out of FBLOCK into the store area. CHKDIR Checks for the presence of a directory in a filename in FBLOCK, and returns the directory character, if there is one. SETDIR Sets a directory into a filename in FBLOCK. STXPRE Stores the required prefix with a filename in FBLOCK. OPFILE Gives access to the filing system OSFIND call. OSHCAL Gives access to the filing system OSFILE call. XOSARG Vector with error trapping to OSARGS. XOSBGE Vector with error trapping to OSBGET. XOSBPU Vector with error trapping to OSBPUT. XOSCLS Vector with error trapping to OSFIND with A=0; used to close a file. XOSGBP Vector with error trapping to OSGBPB. CALUTI Loads and runs a utility format file. MOVFBK MOVFBK moves a filename from ViewStore's list into FBLOCK. A summary of the filenames available and their offsets is given in table 8. Filenames are not stored with prefixes attached, but they do include the directory. You must use the routine STXPRE to add a prefix to the filename once it has been moved to FBLOCK, before calling one of the file routines. On entry: Y contains the offset of the filename to be moved into FBLOCK. On exit: A,X,Y undefined. MOVNAY This routine is the inverse of MOVFBK; it moves a filename from FBLOCK into ViewStore's list of names. The filename in FBLOCK should not include the prefix when this routine is called. The list of filename offset values is given in table 8. On entry: X contains the offset of the filename area in which the name currently in FBLOCK is to be stored. On exit: A,X,Y undefined. CHKDIR CHKDIR checks whether the filename in FBLOCK has a directory, and if it does, it returns the directory. The filename should not include the prefix when this routine is called. On entry: filename to check in FBLOCK. On exit: CS filename contains directory; directory character found returned in A; offset from beginning of FBLOCK to the directory character is in X. CC no directory found. Y undefined. SETDIR SETDIR forces a directory into the filename in FBLOCK. It doesn't matter whether the filename there contains a directory or not; SETDIR will make space for it. The filename must not include a prefix when SETDIR is called. On entry: A contains directory character to set into name in FBLOCK. On exit: A,X,Y undefined. STXPRE This is the routine that you use to add a prefix to a name. The name itself, including directory, should be in FBLOCK. Normally, adding the prefix to a name is the last thing you do before calling the filing system to do some operation on the file: opening or deleting the file, for example. Names themselves should be stored without prefix attached, the prefix being added only when calling the filing system itself. A list of the prefix offsets for different file types is given in table 7. On entry: X contains the offset of the prefix required. On exit: A,X,Y undefined. OPFILE OPFILE is an equivalent of the filing system "OSFIND" call, used for opening files. It assumes, however, that the filename is ready in FBLOCK. OPFILE cannot be used to close a file, as OSFIND can; use the XOSCLS call to close a file. OPFILE also uses the ViewStore error trapping system. On entry: A contains file open code, as for OSFIND; eg. &40 is open for input. On exit: VS error occurred; error code in A. VC no error occurred; file handle is in both A and Y registers; usual OSFIND error of file handle being zero when the file can't be found is trapped: V is set, and the ViewStore "File not found" error code is in A; Y is zero. X undefined. OSHCAL OSHCAL gives the utility access to the filing system OSFILE routine. It uses the OSFARA area as its control block. Note that this spills over into the LWORK area, which will be corrupted after an OSHCAL call. OSHCAL assumes that the filename (when required) is set up in FBLOCK. OSHCAL also sets the high order addresses into the control block at OSFARA +4 and +5; +&C and +&D; and at +&10 and +&11. ViewStore error trapping is also enabled. On entry: A contains reason code as for normal OSFILE (see filing system manual). OSFARA set up with whatever the reason code action requires, eg. start and end addresses for file save. On exit: VS error occurred; error code in A. VC completed successfully; file type in A when relevant. X,Y undefined. XOSARG XOSARG is equivalent to OSARGS, except that ViewStore error handling is enabled. On entry: See filing system manual for OSARGS. On exit: VS error occurred; error code in A. VC no error; see filing system manual for results. PART 2 IN THE NEXT 8BS ISSUE