//$Id: NOTES.TXT 1.3 1998/01/29 07:10:18 ska Exp $
Some guidelines about to write functions:
1) Use the C language instead of assembly or C++ or a language
	constructed after other languages with the help of the macro concept
	of C (e.g.  some people like to write an if statement like:
		IF expr THEN
		  BEGIN
		    statements
		  END
		ELSE
		  BEGIN
		    statements
		  END
	such misuse of the macro concept will puzzle maintainers of the code.)

2) Write the C source highly structured. Don't avoid constructs of the
	language for principle only, e.g. multiple assignments, in-expression
	assigns, post/pre-fix in/decrements, multiline statements. Don't
	avoid code that violates a principle of manually optimizing the
	source for speed or size, but always try to find the smallest
	and efficient _algorithm_ rather the smallest and efficient
	_implementation_ of this algorithm.

3) Use strict prototyping _always_. Prototypes will make life a bit harder
	but they also keep track of several type-related problems. Though,
	strict prototyping will conflict with variable but constant argument
	lists, see below.

4) Try to always identify non-modified arguments of a functions, though,
	keep in mind that this means that these arguments have to remain
	non-modified for the rest of this CLib's life. To demand that all
	arguments remain unmodified seems to be best, but the question is
	if this is worth the effort. Don't avoid to save arguments into
	temporary registers for the argument of optimization; don't avoid
	make direct access to the arguments, either.
	There is one optimization that _may_ profit from non-modifed
	arguments:
		consider these statements:
			fct1(expr1, expr2);
			fct2(expr1, expr2);
		both expr are quite expensive but contain no side effects and
		both expressions are independed of any global variables or
		pointers that are used by fct1(). This means that after returning
		from fct1() expr1 and expr2 will be the same as before the call,
		that's why there is no need to re-evaluate both expressions and
		instead of pop'ing them out of the stack & discarding the already
		evaluated results, fct2() can be called immediately.

		Of course fct1() must ensure that the arguments are unmodified
		themselves, thus, the prototype must look like this:
			<type> fct1(<type1> const arg1, <type2> const arg2);
		Note: That the const have to modify the innermost modifier, so
			const char **arg;
			char const **arg;
			char * const * arg;
		are all wrong in this way of viewing.

5) The useage of 'register' is discouraged. The keyword has a little lack of
	interpretation: Does it mean that the value _must_ be placed into a
	register, _must_ be placed if there is an unused register left, _may_
	be placed, or could it be ignored. There are compilers (e.g. the
	standard MIPS C compiler) that will enforce to place the value into
	a register, well there 31 general purpose registers and approx. 2x8 are
	reserved for 'register' values. Thus, don't avoid 'register' for
	principle only, but use the macro REG instead. Don't avoid to ignore
	the keyword for princple of optimization.

6) Keep the prototypes consistent, unmangled of macros or alike. But
	don't avoid tricks you can play, e.g. if you code a function that
	should have a reference as an argument use this:
		#define fct(arg1,arg2,..,argN) fct_(&(arg1),(arg2),..,(argN))
		<type> fct_(<type1> *arg1, <type2> arg2, .., <typeN> argN);
	Note the underscore and the additional parenthizes.
	But keep in mind that fct() has no longer an address! Thus, fct alone
	don't work anymore and this function cannot directly be used within
	function arrays. This restriction has to be documented!
	Like open(), there are functions that have a variant but constant
	length of their argument list. One implementation looks like this:
		extern <type> fct();
	This tells the compiler that the arguments must not be promoted as
	fct() has no dynamic argument list, but the compiler shall not
	check the types of the arguments themselves.
	The definition of the function looks like:
		<type> fct(<type1> arg1, .., <typeN> argN)
		{}
	N is the maximum number of arguments this fct() can have.
	There are two restrictments to this approach:
	a) The variables must be put right to left onto the stack.
	b) If fct() is called with less than N arguments, these arguments
		are not available within the function's code. This is not the
		problem if the argument is never modified.
	The advantage of this way is that the cumbreos way with variable
	argument lists (va_list, va_arg ..) can be avoided.

	To code such function use this technique:
		Within the header files use the complete prototype by default:
		#ifdef __IN_<fct>
		extern <type> <fct>();
		#else
		extern <type> <fct>(<type1> arg1, .., <typeN> argN, ...);
		#endif

		N is the maximal number of arguments that must be always present.
		The __IN_<fct> must be only defined within the C source file that
		defines the function.
	
	For portability reason provide both techniques with the definition,
	the one using <vararg.h> or <stdarg.h> and the one without; the
	choice depends on the macro _USEVARARG.

7) Every function has requirements, e.g. _strend(str) requires that
	str != NULL. Write an assert() statement wherever you wish, but
	write one for each argument at the top of the function body.
	Try to write one assert() per argument, better one per requirement.
	assert() is automatically removed if the macro NDEBUG is defined, thus,
	it leaves behind _nothing_ but the empty statement ';'.
	The user might want to compile the library with assert enabled for
	debugging purpose.

8) The header files are _not_ the right place to put descriptions to
	the entries.

9) Pointers are a bit tricky as there are different interpretations about
	when to signal "invalid pointer". Some architectures never detect
	invalid pointers, some when invalid memory is accessed, some when
	an address register is loaded, thus, to store an invalid pointer into
	a variable may cause unpredictable results, e.g.:
		fct(char *string)
		{	char *p;

			p = string - 1;

			...
		}

	Unless (the very rare case) that one does pass the middle of a
	string into fct(), the assignment _may_ cause an "invalid pointer"
	(or "segmentation violation" or "bus error" or "protection fault")
	signal, mostly aborting the program, too.
	Though, within DOS this is an unlikely behaviour so try to avoid it,
	but don't avoid it for the only reason to avoid it.

10) int vs. word vs. short vs. int16 vs. size_t vs. long
	Well, there are a number of aliases of the same thing:
		int - The native size of the processor in its current mode
		short - an integer type with _at least_ 16 bit
		long - an integer type with _at least_ 32 bit
		int16 - an alias to a type _exactly_ 16 bit
		word - an alias to a type to access the OS's "word" parameters
		size_t - an ANSI type primarily returned by sizeof()
			 though every "size" value must also has this type (ergo:
			 all mem*() & strn*() functions)
			 though there is one problem about the sign, it is allowed
			 to interprete size_t signed and unsigned.
	Use always the proper alias for your variable:
		int - if you just want to have a value
		short - never; unless perhaps (ONLY perhaps) for storing char's,
			flags and the like
		int16 - if you need a value of exact 16 bit (rare I hope)
		word - for structures directly derived from the OS
		size_t - for ranges, sizes of memory areas; though, take care
			if the functions you use perform correctly interpretation
			of size_t signed and unsigned. Assume size_t as defined
			unsigned.

11) Keep the functions reentrant, unless the specification notes otherwise,
e.g. strtok(). Avoid global variables if possible. If one is necessary, keep
the useage of this variable limited to a set of functions. Foresee that some
access control statements must be inserted in the future to make a multitasking CLib.

12) char vs. signed char vs. unsigned char vs. byte vs. char_t vs. int8
Keep in mind that (char)'s is neither definitely signed or unsigned.
Keep also in mind that current computer's character sets are 8bit, thus,
reading a character may return a negative value in some cases, an
unsigned in other cases. Keep in mind, too, that some locales will use
wide characters or multibyte characters.

Try to stick to the xxx type for:
	byte - when accessing a BYTE value from the OS
	int8 - when using a signed value with exactly 8 bits
	uint8 - when using an unsigned value with exactly 8 bits
	char_t - when processing strings interacting with the user
	char - (any variant) for other strings; choose the signed/unsigned
		modifier if your code relies on sign or not
	int - (any variant) for values with at least 8 bits
	short - (any variants) instead of int, if many values are to be
		stored

13) boolean values (flags, switches)
Use the (boolean) type for variables with a range of 0..1. boolean is:
typedef enum { false = 0, true = 1} boolean;
Never compare a boolean value with 'true'! Use only 'false' or drop
the compare itself, e.g.:
	if(flag != false)	-->		if(flag)
-and-
	if(flag == false)	-->		if(!flag)
