This library uses the parameter passing mecanism of the standard c library: in a procedure call of the form aa = _proc(bb, cc); the assembler code would be ; evaluate cc to hl (or de or bc) push hl ; evaluate bb to hl push hl call _proc pop de pop de ; restore stack ; ; aa is returned in hl Through any of these procedure calls, bc, ix & iy are guaranteed, de can be destroyed, and the returned value comes back in hl, otherwise hl will be destroyed. Strings are passed as pointers to arrays of characters, so to set a string on the stack, a typical sequence might be: ld hl,str .dseg str: db 'This is a string\0' NOTE the 0 byte to end it .cseg push hl The parameter passed is the address of the string, and strings are always terminated by a zero byte. As shown above, when there are multiple arguments, they are pushed in reverse order: i.e. the last (rightmost) argument is pushed first, then the next going left, and so on till the leftmost argument has been pushed. Note also that these words pushed on the stack are sometimes destroyed, so in a case such as: ld hl,(_value) push hl call _something pop de de cannot be guaranteed to contain (_value) after it has been popped. In all calls where a filename is used (_open, _fopen, _scnwld, etc.) a user number can be included: so A3:FILE.TXT will open on drive A: user area 3, and 7:OTHER.FIL will use the current drive, but user area 7. _open: _open("name", fcb) Open a file for block based (i.e. 128 byte chunk) I/O. The first parameter is the name of the file, the second is the address of a 38 byte area of memory to use to hold the fcb. If the call succedes, the fcb address is returned, otherwise -1 is returned. This call will fail if the name is not a valid CP/M filename, or if the file does not exist. _creat: _creat("name", fcb) Create is similar to open, except that if the file exists it is truncated to zero length. As open, it returns the fcb, or -1 for an error. Errors are: bad name, no room in directory, or file exists and is read only. _close: _close(fcb) After a file has been written or modified, this call closes the file. _read: _read(fcb, buffer, nbl) Read takes and fcb, the address of a buffer, and a count of blocks to read, and reads the number specified into the buffer. It returns the number of blocks (128 bytes per block) read, which is usually the same as the number specified, except at end of file. For example if a file holds seven blocks, _read(fcb, buffer, 4) returns 4, reading 4 blocks, the next _read(fcb, buffer, 4) only returns 3, and all subsequent reads will return zero. _write: _write(fcb, buffer, nbl) Just like read, except that it writes to the file. Consider it an error (usually disk full) if write returns less than the number specified. _tell: _tell(fcb) Tell returns the number of the next block to be written or read in the fcb given. Note that files bigger than 65536 records will cause trouble, as the largest value that can be returned is a 16 bit integer. _seek: _seek(fcb, offset, whence) Move the read / write position around in the file. This permits random access to the file, by specifying that reads and writes happen in different places. offset is a number of blocks which may be negative. whence specifies how the offset is to be applied: if 0 the offset is from the beginning of the file, and should be greater than or equal to zero, if 1 it is relative to the current position, and can take any value, and if 2, offset is worked relative to the end of the file. To give some examples: _seek(fcb, 0, 0) rewinds the file to the start, seek(fcb, 15, 0) will access block 15 (blocks are numbered from zero), seek(fcb, -1, 1) steps back a block, causing the most recently processed block to be used again, seek(fcb, 0, 2) moves to the end of the file. _printf _sprintf _fprintf _xprintf: These four form a family of routines used for formatted output. Each one takes a format string and a list of arguments to be output with the format, but they all output in different ways. They all scan the format string, and copy characters one for one, until a '%' is encountered. The '%' means that an argument is to be converted, and a letter is used to specify the type of conversion. %c simply outputs the argument as a character, %s assumes that the argument is the address of a string, and outputs the string, %d prints the argument as a signed decimal number, %u prints it as an unsigned decimal number, %x prints as a hexadecimal number, %o prints in octal, and %b prints in binary. In order to print a '%' character, use '%%' in the format string: this prints a '%', but does not use an argument in the way that (say) %d does. In addition, by specifying a number between the '%' and the format specifier, a minimum field width can be given: the output will be padded so that it's size is at least as big as given: so %5d would convert 100 to " 100". Note that if the field width is too small it is not an error, output continues till everything has been printed, so %2d with 1234 would print "1234". In addition if the width specifier has a leading zero (e.g. %06x) numeric output is zero padded instead of being space filled: %04x with 1024 (400 hex) would print "0400", whereas %4x would just print " 400". Field width is always ignored for character conversion - %c outputs exactly one character. Field width can be supplied dynamically: if the width is given as a '*' then an argument is consumed to provide the width. Note that argument conversion procedes from left to right, and in the case of a '*' width specifier, the width is taken first. Note that a '0' can still be given before a '*' to create zero fill: %0*o with 10 and 2048 will print "0000004000", and the 10 should be on the left of the 2048. To provide a full example: _printf("%d hello %% >%6s< %04x ]%*u[", 42, "xyz", 100, 6, -1) would output "42 hello % > xyz< 0064 ] 65535[" and return 33. All four routines return the number of characters output. As shown above, _printf expects to have all the arguments pushed, and the format as the last thing. The other three expect an additional parameter prior to the format: _fprintf expects a file pointer as returned by _fopen (q.v.), _sprintf expects that address of a buffer: instead of outputting the data, it is placed in the buffer, and a zero byte is added at the end to terminate the string, and _xprintf expects to receive the address of a function to call to deal with generated characters. What will happen is that _xprintf will push a word containing the character, and then call the supplied function. This function MUST adhere to the standard calling convention (preserve bc, ix, iy), and the word pushed is cleaned up by _xprintf. So a _fprintf call might be: _fprintf(fp, "Error: %s", string) where fp is the file pointer. _fprintf, _sprintf and _xprintf all expect their respective additional parameters to be first (i.e. pushed last). Note also that none of these add a trailing newline: if such is desired it must be included in the format string, and it is acceptable to output several lines with one _printf call by having several newlines in the format string. _puts: _puts(string) _fputs: _fputs(string, fp) These two are used to simply print a string, no conversion of any kind is done, they just send out the characters till a zero byte is found. _puts sends to the screen, whereas _fputs sends to the file specified with fp - which was obtained from _fopen (q.v.) _gets: _gets(buffer) _fgets: _fgets(buffer, fp) These two read a line from the keyboard (_gets) or a file (_fgets). In both cases the end of line ( for the keyboard, or for file input) is not part of the line, so if the line above were input, the last character would be the 'r' of the second word 'for'. The strings are returned with a zero byte to mark the end, and both routines return the number of characters in the buffer (i.e. an empty lines gives zero). As a special case, _fgets returns -1 to signify end of file. _fwrite: n2 = _fwrite(fp, buff, n1); _fwrite & _fread (see below) provide raw file access something like _write and _read, except that they use file pointers as provided by _fopen (q.v.). Note that these routines use repeated calls to _putc & _getc (qq.v.) to interface to the file, so to prevent compression/expansion, the file should be open in object mode. In the above example, fp is a file pointer as returned by _fopen, buff is the address of the buffer used for the data transfer, and n1 is the number of bytes to be transferred, n2 is the number of bytes actually transferred, consider it an error if n2 is not equal to n1. _fread: n2 = _fread(fp, buff, n1); read n1 bytes to buff from file accessed through fp, n2 will differ from n1 on an end of file condition, and after that successive calls will return n2 as zero. _fopen: fp = fopen(name, mode, buffer); In this example, name is a standard CP/M name, presented as a string with a zero byte to end it, mode is the mode in which the file is opened: "r" for read, "w" for write, "a" for append. These modes are passed as strings. Because _getc and _putc usually expects to work on text files, they usually do compression/expansion. To prevent this, for reading an executable file byte by byte, prefix the mode by an 'o' char: "or", "ow", "oa". _fopen expects to be handed a buffer which is used for all the I/O for the file in question - the address of this buffer is the third parameter. The buffer should be fbsize bytes long, as defined in STDHDR.I. A typical calling sequence might be: ; assume the name pointer is already in hl ld de,buff ; get buffer address .useg buff: ds fbsize .cseg push de ; & save it ld de,mode .dseg mode: db 'r\0' .cseg push de ; save the mode push hl ; and the filename call _fopen pop de pop de pop de ; restore the stack ld a,h or l ; check for NULL in hl jr z,error ; error if so If _fopen goes ok, the third arg is returned UNLESS the name is the name of a logical CP/M device (CON: RDR: etc.), in which case a special fp is returned. _putc, _getc, _fprintf etc. etc. treat these special fp's just like normal file fp's, so there is nothing special that needs to be done to use them. If there was an error, hl is returned with zero in it. There is a special CP/M device NUL: if it is opened for input then it returns continuous EOF's, and when opened for output, whatever it is handed vanishes into a black hole. The full list of CP/M devices is: "CON:", "r" - read from keyboard, "CON:", "w" - write to screen, "RDR:", "r" - read from modem port, "PUN:", "w" - write to modem port, "LST:", "w" - write to printer port, plus "NUL:" for both read and write as specified above. In addition, the file BDOS.I holds .var variable definitions for these special devices: stdin is standard input, stdout is standard output. stdrdr, stdpun, stdlst, are the RDR:, PUN:, and LST: ports respectively; stderr is the screen, and stdkbd is the keyboard. stdin defaults to the same as stdkbd, and stdout defaults to the same as stderr, except that it is possible to "redirect" input or output, by using RARX.O or PARX.O. If RARX.O is used and the program is invoked A>PROG , so it is not necessary to _putchar an explicit '\r' (carriage return). _putc(ch, fp) sends ch to file fp: fp is returned from _fopen. Both of these return the output character in hl, except that if _putc is writing to a file, and the write fails (usually because of disk full, or a file open for read), then it will return EOF, or -1 to signify the error. _putchar usually writes to the screen unless output redirection has occurred - see fopen above and ARX.DOC for an explanation of redirection. _kbhit: test = _kbhit(); This checks if there is a character waiting at the keyboard, and returns true if there is keyboard input waiting to be processed. _getchar: ch = _getchar(); _getc: ch = _getc(fp); _getchar() returns the next character from stdin, which will respect redirection, and _getc(fp) gets the next character from the file referenced by fp. As does _putc, _getc knows about stdrdr etc. etc. _ungetc: ch = ungetc(ch, fp) This "pushes" a character back onto an input stream: the next call to _getc for the fp given returns ch, just as if it were a character in the file. This is guaranteed only for one char push back, and only if at least one character has been read. It returns the pushed back character, or EOF on an error. fp must be a read filepointer returned by fopen, or one of the stdin / stdkbd read filepointers. _stty: _stty(fp, mode); _stty changes the "mode" of CP/M devices and files: for all CP/M "std" devices except stdkbd (keyboard input), mode is 0 => do compression / expansion, or 1 => don't. stdkbd allows four modes: 0 => normal buffered input (use BDOS call for buffered input) 1 => unbuffered input with mapped to 2 => unbuffered input without mapping 3 => unbuffered input without echo for files: 0 => change to text mode (i.e. fopen (name, "r" / "w" / "a", ...) 1 => change to object mode (i.e. fopen (name, "or" / "ow" / "oa", ...) Note for files, text mode means that 0x1a (control Z) flags an end of file, and that pairs come in as just a single (newline). Object mode means read the file as is, i.e. no compression of pairs, and EOF is only flagged when the physical end of file is hit (i.e. last character in the last block). _scnwld: count = _scnwld(name, function) This processes a CP/M wildcard file specifications: _scnwld takes the wildcard name (a la B:*.* or C:ABC*.?Q?), and calls function once for each file that matches, returning the count of calls that it made. function should expect to receive the filename as a string pushed immediately before the call, and must rspect the usual calling convention (i.e. preserve bc, ix, iy). _pfile: name = _pfile(fcb); This decodes the filename from a CP/M fcb as might be given back by _open, (or by directly creating a CP/M fcb), or a fp given by _fopen. name will be a pointer to a string that contains the decoded name, given that fcb points to either a CP/M fcb (i.e. what open returns), or to a fp. The string is returned in a buffer that is valid to the next call of _pfile, at which time it will be overwritten. Note that several other routines use this same buffer: notably _itoa and _scrypt (in xlib.l). Because of this, the best thing to do is to use the buffer immediately: either print it (_puts or _printf), or use _strcpy (q.v.) to make a copy in a local buffer. _stat: mode = stat(file, mask, bits); Changes the R/O and SYS bits of the file, whose name is passed as a string with a zero byte at the end; and returns the new value. The R/O bit is bit 0, and the SYS bit is bit 1. To change either of these bits, set the corresponding bit in mask, and provide the desired value in bits: i.e. mask == 1, bits == 1 would set the file R/O, without altering the SYS bit, and would return the new bit pair. In particular, this means it is possible to enquire the current value by just setting mask to zero. _swapin: size = _swapin(file, address) This loads the file given into memory at the specified address, returning the size in bytes, or -1 on an error - file not found or bad filename. If the filename has no extension then swapin wil automatically add a .OVR to it. _upper: _upper(str) _lower: _lower(str) These expect the address of a zero byte terminated string, and on return the string will be completely in upper or lower case, as appropriate. _inchr: offset = _inchr(str, ch) This finds the offset into a string of a character: it returns the position of first occurence of ch in str, or -1 if no match: so _inchr("hello", 'l') would return 2 (bytes are numbered from zero), and _inchr("hello", 'q') would return -1. _instr: offset = _instr(str, sub) This is just like _inchr, except that it looks for a substring rather than a single character, returns count to first char of substring, or -1 on no match: _instr("hello", "lo") returns 3 (bytes numbered from zero), whereas _instr("hello", "elo") returns -1. _rinchr: offset = _rinchr(str, ch) _rinstr: offset = _rinstr(str, sub) These two are just like _inchr and _instr above, except that instead of giving the offset to the first occurance, they give the offset to the last, so for example _rinchr("hello", 'l') returns 3. Note that they still return -1 if no match is to be found. _index: addr = _index(str, ch) _insdex: addr = _insdex(str, sub) _rindex: addr = _rindex(str, ch) _rinsdex: addr = _rinsdex(str, sub) These four match the above four one to one (_inchr and _index, _instr and _insdex, etc.) except that instead of returning the offset into the string, they return the actual address of the first or last occurance, as appropriate. Note also that in a no match situation, they return zero rather than -1. _tinstr: offset = _tinstr(str, sub) This finds a substring in a string as a token: it is similar to _instr, but it also inspects the context of any substring it finds: if either end of the substring is an alphanumeric (first or last char) and the char in the main string that extends just beyond that char is also alphanumeric then the substring is rejected: _tinstr("...push.de", "pu") would return -1: there is an 's' after the 'u' in the main string, whereas: _tinstr("...push.de", "de") would work: _tinstr knows about the ends of the main string. _nconv: _nconv(str) This converts a string with alphabetic words to "name" format. What it does is to scan for groups of alphabetic characters in either upper or lower case, surrounded by non alphabetic characters. What it then does is to force the first letter in each of these groups to be upper case, while converting the rest to lower case: so "HeLLo woRLd" would become "Hello World". _prefix: test = _prefix(str, sub); _suffix: test = _suffix(str, sub); This checks to see if sub is a prefix / suffix of str, and returns true if so, false if not. Hence _prefix("Hello World", "Hel") returns true, whereas prefix("Hello World", "xyz") returns false. _chrcnt: count = _chrcnt(str, ch) _chrcnt returns a count of how many times character ch occurs in string str: _chrcnt("Hello World", 'l') returns 3: there are three 'l's in the string. _chrcat: _chrcat(str, ch) This takes character ch, and appends it to str: so if str contained "hel" on entry, and ch was 'p', str on exit would be "help", complete with a new trailing zero byte. _chrcat assumes that the string pointer provided addresses a buffer with sufficient space: it does no error checking at all. _detab: _detab(dest, src) _entab: _entab(dest, src) _detab copies from string src, making a new string at dest, but as it copies it converts tabs to the correct number of spaces. It assumes that tab stops are every eight characters. _entab reverses this operation: it converts groups of spaces to tabs wherever possible. Note that entab can be given the same string as both source and destination, but if this is tried with _detab it may cause a program crash. _strip: _strip(str) _strip simply removes any trailing white space from str: it does this by placing a new zero byte after the last non-white space character in str. _trim: _trim(str, n) _trim checks the length of string str, and if it is greater than n then a zero byte is inserted to make its length equal to n. If its length is less than n then no action is taken. _byp: dest = _byp(str) _unbyp: dest = _unbyp(str) _byp returns the address of the first non-white space character in its argument string, effectively bypassing leading white space. _unbyp is similar, except that it returns the address of the first white space character: instead of stepping over white space, it steps over everything but white space. _strcmp: val = _strcmp(s, t) _strcmp returns a value telling whether s is greater than, equal to, or less than t, i.e. it returns a number greater than zero if s comes after t in a dictionary, 0 if they are the same, and a negative number if s comes before t. Full ASCII ordering is used: the strings can containany ASCII character from ^A (0x01) to DEL (0x7f). Characters above DEL can cause unpredictable results in ordering, however differences are always detected. Note that for equality s need not be the same as t, what is being tested is if the bytes addressed by the two pointers are the same. _strequ: val = _strequ(s, t) Check if s and t point to similar strings: return true (i.e. non-zero) if so, false (zero) if not. _strlen: val = _strlen(s) This gets the length of a string, and return it as an integer. _strcpy: _strcpy(s, t) _strcpy reads the string at t and makes a copy of it in the buffer addressed by s. This is useful to save permanent copies of the strings returned by procedures such as _pfile or _itoa (qq.v.). Note that no error checking is done: s must address enough memory to receive the string, also unpredictable results may occur if s and t overlap. _strcat: strcat(s, t) This takes string t, and adds it to the end of string s. It leaves the current contents of s alone, up to but not including the terminating zero. (What happens is that the terminating zero on s gets replaced by the first char of t, and the rest of t is copied over up to and including the zero on the end of t. As in _strcpy it is the responsibility of the caller to ensure that there is enough memory addressed by s to receive the new bytes, and the caller should ensure that s and t don't overlap. _strrev: _strrev(s) This takes its argument string and reverses the bytes in place: so on entry if s pointed to "hello", on exit it would point to "olleh". The terminating zero byte is left undisturbed. _strswp: _strswp(s, t) _strswp moves the bytes addressed by t to s, and the bytes addressed by s to t effectively swapping the two strings. Note that it there has to be enough memory to do this, if s is shorter than t, there must still be enough memory in the buffer addressed by s to take all of t. As in _strcpy and _strcat, unpredictable results will follow if s and t overlap. _strmov: _strmov(s, t) This is very similar to _strcat in that it moves the string t to s, however unlike _strcpy it does not move the zero byte terminator. This makes it useful for replacing a small part of a large string without disturbing the rest of it. _strncmp: val = _strncmp(s, t, n) Just like _strcmp, this compares two strings, however it also takes a third parameter which is a maximum count to compare: i.e. if no differences have found in the first n bytes then the strings are considered equal, and zero gets returned. _strnequ: val = _strnequ(s, t, n) This is like _strequ: it tests for equality, but it stops after n bytes of testing, and returns true if no differences were found. _strncpy: _strncpy(s, t, n) This copies string t to string s, but will copy no more than n characters. Note that it will at most move n + 1 characters because it always adds a trailing zero, and this is not included in the count. _strncat: _strncat(s, t, n) This adds t to the end of s, but like _strncpy it stops after n characters. As in _strncpy, the zero terminator is always added, but never included in the count n. _strnrev: _strnrev(s, n) This reverses the string s, but if it's length exceeds n then only the first n bytes are affected, the remainder are left undisturbed. For example if s points to "hello", and n is 3, then on exit s would point to "lehlo": the first three bytes in the buffer have been reversed and the rest is left alone. _strnmov: _strnmov(s, t, n) This does a limited (i.e. max n bytes move) string copy from t to s, however like _strmov it does not move the zero byte terminator. _memcmp: val = _memcmp(s, t, n) This is similar to _strncmp in that it compares the bytes in two memory buffers, and is set to compare a maximum of n bytes, however unlike _strncmp it will not stop at the end of a string, it always compares exactly n bytes. _memequ: val = _memequ(s, t, n) Like _strnequ this checks memory for equality, however like _memcmp it does not stop at the end of a string, it keeps going till it has checked n bytes. _memcpy: _memcpy(s, t, n) Move bytes from t to s: this simply moves n bytes, no more and no less. Note that for this procedure, s and t can overlap: checks are made before the copy is started so that data is moved in the right order (head first or tail first). _memrev: _memrev(s, n) This just reverses n bytes addressed by s - like _strnrev but without the checking for end of string. _memset: _memset(s, v, n) Set n bytes, addressed by s, to contain value v. _memswp: _memswp(s, t, n) This just swaps the first n bytes addressed by s and t. _toupper: i = _toupper(ch) This returns ch untouched unless ch is lower case ('a' - 'z'), in which case it is converted to upper case. Note that for _toupper, _tolower and all the _is????? routines, only the least significant byte of the argument is considered, i.e. if hl is used to push it, then only l will be significant: h can contain anything. _tolower: i = _tolower(ch) Returns ch converted to lower case if necessary. _isupper: i = _isupper(ch) Returns true (non-zero) if ch is an upper case letter, false otherwise. _islower: i = _islower(ch) Returns true if ch is a lower case letter. _isdigit: i = _isdigit(ch) Returns true if ch is a digit ('0' - '9') _isprint: i = _isprint(ch) Returns true if ch is a printable char (' ' - '~') _isgraph: i = _isgraph(ch) Returns true if ch is a graphic char ('!' - '~') _isascii: i = _isascii(ch) Returns true if ch is a valid ascii char (0 - 0x7f) _isalpha: i = _isalpha(ch) Returns true if ch is alphabetic ('A' - 'Z' or 'a' - 'z') _isalnum: i = _isalnum(ch) Returns true if ch is alphanumeric ('A' - 'Z', 'a' - 'z' or '0' - '9') _isxdigit: i = _isxdigit(ch) Returns true if ch is a hexadecimal digit ('A' - 'F', 'a' - 'f' or '0' - '9') _isspace: i = _isspace(ch) Returns true if ch is a white space char (tab - 0x09, lf - 0x0a, ff - 0x0c, cr - 0x0d, or ' ') _iscntrl: i = _iscntrl(ch) Returns true if ch is a control char (0 - 0x1f or 0x7f) _ispunct: i = _ispunct(ch) Returns true if ch is a punctuation char: i.e. not a control char or an alphanumeric _atoi: val = _atoi(s) If string s contains a number (e.g. "1234") then _atoi returns the value of this number: a leading '-' creates a negative number. Leading white space in the string is skipped. _atoix: val = _atoix(s) This is just like _atoi in that it converts a string to a number, but it also recognises hexadecimal constants starting with 0x (eg 0x7fff), or octal (leading 0) or binary (leading 0b). Hence "76", "0x4c", "0114" and "0b1001100" all give the same value. As in _atoi, leading white space is skipped. _itoa: str = _itoa(val) This reverses atoi: it takes an integer argument, and returns the address of a buffer that holds the number printed as a string. This is the same buffer as used by _pfile, so the same precautions should be observed regarding use and saving of the string. _isqr: i = _isqr(j) _isqr returns the integer square root of an unsigned argument. _abs: i = _abs(j) _abs returns the absolute value of its argument taken as a signed integer. _sgn: i = _sgn(j) _sgn returns the "sign" of its argument taken as a signed integer: if it is greater than 0 then 1 is returned, if less than zero -1, if zero then zero is returned. _min: i = _min(j, k) _max: i = _max(j, k) These return the minimum or maximum (as appropriate) of two signed integer arguments. _umin: i = _umin(j, k) _umax: i = _umax(j, k) These return the minimum or maximum (as appropriate) of two unsigned integer arguments. _lcm: i = _lcm(j, k) This returns the lowest common multiple of two integer arguments, i.e. the smallest number that both can divide. Note that if such a number is greater than 65535, then the return value will be undefined. _gcd: i = _gcd(j, k) This returns the greatest common divisor of two arguments, i.e. the biggest number that will divide into both. _swab: _swab(dest, src, n) This transfers n bytes from src to dest (like _memcpy), but it swaps adjecent bytes as it goes. Note that if n is given odd, it is reduced by 1 to make it even. _bdos: hl = _bdos(c, de) This calls into bdos, with arguments as shown, returns value is what bdos gives back in hl. _bios: a = bios(n, bc, de) This calls the nth entry in bios table (1 is warm boot), with bc and de registers set as shown. The return value is whatever comes back in a or hl as appropriate to the routine in question. _inp: ch = _inp(port) This inputs a byte from one of the Z80's I/O ports. _outp: _outp(ch, port) This output a byte to one of the Z80's I/O ports. _signal: _signal(mode) This manipulates the way that ^C's are handled. mode can be one of three things: if zero then normal ^C trapping is enabled, i.e. a reboot occurs; if 1 then ^C's are ignored:, in fact the jump to zero is intercepted, and control is passed back into bdos to do the input again; any other value is taken as the address of a jump_buffer created by _setjmp, which is used to cause a _longjmp to the environment saved in the jump_buffer. _setjmp: val = _setjmp(env); When called in normal flow, _setjmp sets up data for a non-local goto. It saves it's environment in the array provided (which must be 4 words long), and returns zero. _longjmp: _longjmp(env, val); This executes a non-local goto, which also cleans up the stack & frame pointers: instead of returning to the caller of _longjmp, this call causes execution to continue as if the call to _setjmp that initiated the environment env had just returned. Note however that the value "returned" by _setjmp in this case is val, which if non-zero provides a means to determine if it was a "setup" call to _setjmp or a subsequent call to _longjmp somewhere else. This call restores sp, bc, and ix to their original values. If the environment is no longer valid then control returns back out of _longjmp - this in itself implies an error. _resrv: _resrv(n) Successive calls to _sbrk keep on taking more memory, this determines how much should be reserved for the stack, without any calls to _resrv, the default is 1K, however it can be made as small or large as is needed. _sbrk: ptr = _sbrk(n) This returns a pointer to a block of memory n bytes long that can be used exclusively by the caller of sbrk. Note that successive calls return higher and higher addresses, and when the space available is exhausted, _sbrk returns -1 to show the error. Note also that successive blocks will be contiguous, so space for a string can be dynamically allocated by repeated _sbrk(1) calls. _sleep: _sleep(n) This just waits for n seconds. _pause: _pause(n) This waits for n milliseconds - these previous two assume a 4MHz Z80 - with other processors / clock speeds, the constant will have to be altered in _pause. _pack: _pack(p, r, n) _unpack: _unpack(p, r, n) These two handle rad50 compression: pack reads plain text from p, and packs it rad50 into r, packing a total of n triplets. _unpack reverses this: it unpacks n rad50 triplets reading from r, and placing the unpacked plain text at p. _call: hl = _call(addr, hl, a, de, bc) _calla: a = _calla(addr, hl, a, de, bc) These two allow calling of an arbitrary machine language address: they both call the routine at addr, with hl, a, de, and bc set as shown, however _call returns whatever came back in hl, whereas _calla returns whatever came back in a. _exit: _exit This is not a procedure per se, instead each of the arx.o modules have _exit defined in such a way that a call to _exit will terminate the program, closing stdout properly if needed. The rest of these routines are system subroutines, these all have a # prefix. #mul: integer multiply - returns hl * de in hl - destroys de & a #udiv: unsigned integer divide - returns hl / de in hl, hl % de in de. hl % de is the remainder on division when hl is divided by de. destroys a #div: signed integer divide - de and hl behave as above, also destroys a #muldiv: returns hl * de / bc in hl, with remainder in de, this will give better accuracy than calls to #mul & #div as it retains a full 32 bit inner product. destroys a & bc #shl: shift left: returns hl left shifted de times in hl (with zero fill), destroys de & a #shr: shift right: returns hl right shifted de times in hl, (with zero fill), destroys de & a #rotate: returns hl rotated left de times in hl, destroys de & a #neg: returns -hl in hl, destroys a #cpl: returns bitwise not hl in hl, destroys a (~ in zsm exprs.) #not: returns logical not hl in hl, destroys a (! in zsm exprs.) #and: returns hl and de in hl, destroys a #or: returns hl or de in hl, destroys a #xor: returns hl xor de in hl, destroys a #arg2: after entry to a routine of the type _proc(aa, bb), get aa to de & bb to hl. Correct exit with maintenance of stack discipline (i.e. matching your pushes and pops), is a 'ret' instruction. #arg2b: works like #arg2 getting two arguments to hl & de, except that it also pushes bc, so with correct stack discipline, the return from a routine that enters with an #arg2b would be a 'pop bc' followed by a 'ret'. #arg3: similar to #arg2b: for a routine _proc(aa, bb, cc), get aa to de, bb to hl & cc to bc - leaves old bc on stack so correct exit with maintenance of stack discipline is a 'pop bc' followed by a 'ret'. #csv: save bc & ix on stack & set ix from sp as initialisation for a procedure: allows ix to be used as a frame pointer #cret: any procedure that uses #csv MUST return by jumping to #cret to restore ix & bx - note that stack discipline need not be maintained as long as the data on the stack from ix up has not been altered, and ix itself is unchanged: typical usage is proc: call #csv ld hl,-76 add hl,sp ld sp,hl ; allocate local variables ld l,(ix+6) ld h,(ix+7) ; get the first arg to hl ld (ix-2),l ld (ix-1),h ; save for later etc. etc. etc. jp #cret ; return in the above example, arguments to proc are accessible at ix+6/7, ix+8/9, ix+10/11 etc. for the 1st. 2nd. 3rd. args, and local variable space starts at (ix-1) and proceeds down. sp has been altered by proc, however since sp & ix are equal on exit from #csv, the first thing #cret does is to ld sp,ix hence resetting sp. #csvx, #cretx: expanded csv & cret - in addition to saving ix & bc, these also save iy, hl', de' and bc'; due to increased stack usage arguments start at ix+14/15 etc. #ucsa: force char in a upper case #lcsa: force char in a lower case #iswa: return with z flag set iff char in a is white space #byp #unbyp: these are direct assembler interfaces to _byp and _unbyp: the only difference is that they take the parameter directly in hl, rather than requiring that it be pushed in the stack as is the usual convention. #setcmp: set signed hl and de for an unsigned compare: all this does is to add 0x8000 to both hl and de. For unsigned values it is possible to test hl >= de simply by doing a 'or a' and a 'sbc hl,de' and testing the carry. This does not work on signed values, however if a pair of signed integers are passed into setcmp then the resulting values can be compared by doing a 'sbc hl,de'. Destroys a #setch: do the above, but to hl only: i.e. add 0x8000 to hl. destroys a The following is a list of further external labels that are reserved - these are used by the above routines and their names should not clash with any external labels in the program being linked. #bsave #cbdos #cconv #cpylp #ctop #digfms #digfmt #doprnt #fcb #flags #kbdbf #kbdhd #kbdpc #rdrpc #sbuff #spad #spsav #ssiz #stdin #stdout #wfcb #xxfp #xxstr