Guidelines for adding custom functions
--------------------------------------

Step 0: Determine if is should it be done?

    The main focus for calc is to provide a portable platform for
    multi-precision calculations in a C-like environment.  You should
    consider implementing algorithms in the calc language as a first
    choice.  Sometimes an algorithm requires use of special hardware, a
    non-portable OS or pre-compiled C library.	In these cases a custom
    interface may be needed.

    The custom function interface is intended to make is easy for
    programmers to add functionality that would be otherwise
    un-suitable for general distribution.  Functions that are
    non-portable (machine, hardware or OS dependent) or highly
    specialized are possible candidates for custom functions.

    So before you go to step 1, ask yourself:

	+ Can I implement this as a calc resource file or calc shell script?

	    If Yes, write the shell script or resource file and be done with it.
	    If No, continue to the next question ...

	+ Does it require the use of non-portable features,
	  OS specific support or special hardware?

	    If No, write it as a regular builtin function.
	    If Yes, continue to step 1 ...


Step 1: Do some background work

    First ... read this file ALL THE WAY THROUGH before implementing
    anything in Steps 2 and beyond!

    If you are not familiar with calc internals, we recommend that
    you look at some examples of custom functions.  Look at the
    the following source files:

	custom.c
	custom.h
	custom/custtbl.c
	custom/c_*.[ch]
	custom/*.cal
	help/custom		(or run:  calc help custom)

    You would be well advised to look at a more recent calc source
    such as one available in from the calc version archive.
    See the following for more details:

	help/archive		(or run:  calc help archive)


Step 2: Name your custom function

    We suggest that you pick a name that does not conflict with
    one of the builtin names.  It makes it easier to get help
    via the help interface and avoid confusion down the road.

    You should avoid picking a name that matches a file or
    directory name under ${HELPDIR} as well.  Not all help
    files are associated with builtin function names.

    For purposes of this file, we will use the name 'curds'
    as our example custom function name.


Step 3: Document your custom function

    No this step is NOT out of order.  We recommend that you write the
    help file associated with your new custom function EARLY.  By
    experience we have found that the small amount of effort made to
    write "how the custom function will be used" into a help file pays
    off in a big way when it comes to coding.  Often the effort of
    writing a help file will clarify fuzzy aspects of your design.
    Besides, unless you write the help file first, it will likely never
    be written later on.  :-(

    OK ... we will stop preaching now ...

    [[ From now on we will give filenames relative to the custom directory ]]

    Take a look at one of the example custom help files:

	custom/devnull
	custom/argv
	custom/help
	custom/sysinfo

    You can save time by using one of the custom help files
    as a template.  Copy one of these files to your own help file:

	cd custom
	cp sysinfo curds

    and edit it accordingly.


Step 4: Write your test code

    No this step is NOT out of order either.  We recommend that you
    write a simple calc script that will call your custom function and
    check the results.

    This script will be useful while you are debugging your code.  In
    addition, if you wish to submit your code for distribution, this
    test code will be an import part of your submission.  Your test
    code will also service as additional for your custom function.

    Oops ... we said we would stop preaching, sorry about that ...

    You can use one of the following as a template:

	custom/argv.cal
	custom/halflen.cal

    Copy one of these to your own file:

	cd custom
	cp halflen.cal curds.cal

    and exit it accordingly.  In particular you will want to:

	remove our header disclaimer (or put your own on)

	change the name from halflen() to curds()

	change the comment from 'halflen - determine the length ...' to
	'curds - brief description about ...'

	change other code as needed.


Step 5: Write your custom function

    By convention, the files we ship that contain custom function
    interface code in filenames of the form:

	c_*.c

    We suggest that you use filenames of the form:

	u_*.c

    to avoid filename conflicts.

    We recommend that you use one of the c_*.c files as a template.
    Copy an appropriate file to your file:

	cd custom
	cp c_argv.c u_curds.c

    Before you edit it, you should note that there are several important
    features of this file.

	a) All of the code in the file is found between #if ... #endif:

		/*
		 * only comments and blank lines at the top
		 */

		#if defined(CUSTOM)

		... all code, #includes, #defines etc.

		#endif /* CUSTOM */

	   This allows this code to 'go away' when the upper Makefile
	   disables the custom code (because ALLOW_CUSTOM no longer
	   has the -DCUSTOM define).

	b) The function type must be:

		/*ARGSUSED*/
		VALUE
		u_curds(char *name, int count, VALUE **vals)

	   The 3 args are passed in by the custom interface
	   and have the following meaning:

		name	The name of the custom function that
			was called.  In particular, this is the first
			string arg that was given to the custom()
			builtin.  This is the equivalent of argv[0] for
			main() in C programming.

			The same code can be used for multiple custom
			functions by processing off of this value.

		count	This is the number of additional args that
			was given to the custom() builtin.  Note
			that count does NOT include the name arg.
			This is similar to argc except that count
			is one less than the main() argc interface.

			For example, a call of:

			    custom("curds", a, b, c)

			would cause count to be passed as 3.

		vals	This is a pointer to an array of VALUEs.
			This is the equivalent of argv+1 for
			main() in C programming.  The difference
			here is that vals[0] refers to the 1st
			parameter AFTER the same.

			For example, a call of:

			    custom("curds", a, b, c)

			would cause vals to point to the following array:

			    vals[0]  points to	a
			    vals[1]  points to	b
			    vals[2]  points to	c

	   NOTE: If you do not use any of the 3 function parameters,
	   then you should declare that function parameter to be UNUSED.
	   For example, if the count and vals parameters were not used
	   in your custom function, then your declaration should be:

		/*ARGSUSED*/
		VALUE
		u_curds(char *name, int UNUSED count, VALUE UNUSED **vals)

	c) The return value is the function must be a VALUE.

	   The typical way to form a VALUE to return is by declaring
	   the following local variable:

		VALUE result;	/* what we will return */

	d) You will need to include:

		#if defined(CUSTOM)

		/* any #include <foobar.h> here */

		#include "../have_const.h"
		#include "../value.h"
		#include "custom.h"

		#include "../have_unused.h"

	    Typically these will be included just below any system
	    includes and just below the #if defined(CUSTOM) line.

    To better understand the VALUE type, read:

	../value.h

    The VALUE is a union of major value types found inside calc.
    The v_type VALUE element determines which union element is
    being used.	  Assume that we have:

	VALUE *vp;

    Then the value is determined according to v_type:

	vp->v_type	the value is	which is a	type defined in
	----------	------------	----------	---------------
	V_NULL		(none)		n/a		n/a
	V_INT		vp->v_int	long		n/a
	V_NUM		vp->v_num	NUMBER *	../qmath.h
	V_COM		vp->v_com	COMPLEX *	../cmath.h
	V_ADDR		vp->v_addr	VALUE *		../value.h
	V_STR		vp->v_str	char *		n/a
	V_MAT		vp->v_mat	MATRIX *	../value.h
	V_LIST		vp->v_list	LIST *		../value.h
	V_ASSOC		vp->v_assoc	ASSOC *		../value.h
	V_OBJ		vp->v_obj	OBJECT *	../value.h
	V_FILE		vp->v_file	FILEID		../value.h
	V_RAND		vp->v_rand	RAND *		../zrand.h
	V_RANDOM	vp->v_random	RANDOM *	../zrandom.h
	V_CONFIG	vp->v_config	CONFIG *	../config.h
	V_HASH		vp->v_hash	HASH *		../hash.h
	V_BLOCK		vp->v_block	BLOCK *		../block.h

    The V_OCTET is under review and should not be used at this time.

    There are a number of macros that may be used to determine
    information about the numerical values (ZVALUE, NUMBER and COMPLEX).
    you might also want to read the following to understand
    some of the numerical types of ZVALUE, NUMBER and COMPLEX:

	../zmath.h
	../qmath.h
	../cmath.h

    While we cannot go into full detail here are some cookbook
    code for manipulating VALUEs.  For these examples assume
    that we will manipulate the return value:

	VALUE result;	/* what we will return */

    To return NULL:

	result.v_type = V_NULL;
	return result;

    To return a long you need to convert it to a NUMBER:

	long variable;

	result.v_type = V_NUM;
	result.v_num = itoq(variable);		/* see ../qmath.c */
	return result;

    To return a FULL you need to convert it to a NUMBER:

	FULL variable;

	result.v_type = V_NUM;
	result.v_num = utoq(variable);		/* see ../qmath.c */
	return result;

    To convert a ZVALUE to a NUMBER*:

	ZVALUE variable;

	result.v_type = V_NUM;
	result.v_num = qalloc();		/* see ../qmath.c */
	result.v_num->num = variable;
	return result;

    To convert a small NUMBER* into a long:

	NUMBER *num;
	long variable;

	variable = qtoi(num);

    To obtain a ZVALUE from a NUMBER*, extract the numerator:

	NUMBER *num;
	ZVALUE z_variable;

	if (qisint(num)) {
		z_variable = num->num;
	}

    To be sure that the value will fit, use the ZVALUE test macros:

	ZVALUE z_num;
	long variable;
	unsigned long u_variable;
	FULL f_variable;
	short very_tiny_variable;

	if (zgtmaxlong(z_num)) {			/* see ../zmath.h */
		variable = ztolong(z_num);
	}
	if (zgtmaxulong(z_num)) {
		u_variable = ztoulong(z_num);
	}
	if (zgtmaxufull(z_num)) {
		f_variable = ztofull(z_num);
	}
	if (zistiny(z_num)) {
		very_tiny_variable = z1tol(z_num);
	}

    You can (and should) add debugging statements to your custom code
    by examining bit 8 of the calc_debug config flag:

	if (conf->calc_debug & CALCDBG_CUSTOM) {
	    fprintf(stderr, "%ssome custom debug note: msg\n",
		(conf->tab_ok ? "\t" : ""),
		((msg == NULL) ? "((NULL))" : msg));
	}

    One is able to set bit 8 by way of the calc command line:

    	calc -D 128

    See the calc man page for details.  One may also set that bit
    while running calc by way of the config() builtin function:

    	config("calc_debug", 128);

    See the help/config file for details on calc_debug.

Step 6: Register the function in the custom interface table

    To allow the custom() builtin to transfer control to your function,
    you need to add an entry into the CONST struct custom cust table
    found in custom/custtbl.c:

	/*
	 * custom interface table
	 *
	 * The order of the elements in struct custom are:
	 *
	 *	{ "xyz", "brief description of the xyz custom function",
	 *	   minimum_args, maximum_args, c_xyz },
	 *
	 * where:
	 *
	 *	minimum_args	an int >= 0
	 *	maximum_args	an int >= minimum_args and <= MAX_CUSTOM_ARGS
	 *
	 * Use MAX_CUSTOM_ARGS for maximum_args is the maximum number of args
	 * is potentially 'unlimited'.
	 *
	 * If the brief description cannot fit on the same line as the name
	 * without wrapping on a 80 col window, the description is probably
	 * too long and will not look nice in the show custom output.
	 */
	CONST struct custom cust[] = {

	#if defined(CUSTOM)


		/*
		 * add your own custom functions here
		 *
		 * We suggest that you sort the entries below by name
		 * so that show custom will produce a nice sorted list.
		 */

		{ "argv", "information about its args, returns arg count",
		 0, MAX_CUSTOM_ARGS, c_argv },

		{ "devnull", "does nothing",
		 0, MAX_CUSTOM_ARGS, c_devnull },

		{ "help", "help for custom functions",
		 1, 1, c_help },

		{ "sysinfo", "return a calc #define value",
		 0, 1, c_sysinfo },


	#endif /* CUSTOM */

		/*
		 * This must be at the end of this table!!!
		 */
		{NULL, NULL,
		 0, 0, NULL}
	};

    The definition of struct custom may be found in custom.h.

    It is important that your entry be placed inside the:

	#if defined(CUSTOM) ... #endif /* CUSTOM */

    lines so that when the custom interface is disabled by the upper
    level Makefile, one does not have unsatisfied symbols.

    The brief description should be brief so that 'show custom' looks well
    formatted.	If the brief description cannot fit on the same line as
    the name without wrapping on a 80 col window, the description is
    probably too long and will not look nice in the show custom output.

    The minargs places a lower bound on the number of args that
    must be supplied to the interface.	This does NOT count
    the name argument given to custom().  So if minargs is 2:

	custom("curds")		/* call blocked at high level interface */
	custom("curds", a)	/* call blocked at high level interface */
	custom("curds", a, b)	/* call passed down to "curds" interface */

    The maxargs sets a limit on the number of args that may be passed.
    If minargs == maxargs, then the call requires a fixed number of
    argument.  There is a upper limit on the number of args.  If
    one wants an effectively unlimited upper bound, use MAX_CUSTOM_ARGS.

    Note that one must have:

		0 <= minargs <= maxargs <= MAX_CUSTOM_ARGS

    To allow the curds function to take at least 2 args and up
    to 5 args, one would add the following entry to cust[]:

		{ "curds", "brief description about curds interface",
		 2, 5, u_curds },

    It is recommended that the cust[] remain in alphabetical order,
    so one would place it before the "devnull" and after "argv".

    Last, you must forward declare the u_curds near the top of the file:

	#if defined(CUSTOM)


	/*
	 * add your forward custom function declarations here
	 *
	 * Declare custom functions as follows:
	 *
	 *	E_FUNC VALUE c_xyz(char*, int, VALUE**);
	 *
	 * We suggest that you sort the entries below by name.
	 */
	E_FUNC VALUE c_argv(char*, int, VALUE**);
	E_FUNC VALUE c_devnull(char*, int, VALUE**);
	E_FUNC VALUE c_help(char*, int, VALUE**);
	E_FUNC VALUE c_sysinfo(char*, int, VALUE**);

    For u_curds we would add the line:

	E_FUNC VALUE u_curds(char*, int, VALUE**);


Step 7: Add the required information to the custom/Makefile.head

    The calc test script, curds.cal, should be added to the
    CUSTOM_CALC_FILES Makefile variable found in custom/Makefile.head:

	CUSTOM_CALC_FILES= argv.cal halflen.cal curds.cal

    The help file, curds, should be added to the CUSTOM_HELP
    custom/Makefile.head variable:

	CUSTOM_HELP= argv devnull help sysinfo curds

    If you needed to create any .h files to support u_curds.c, these
    files should be added to the CUSTOM_H_SRC custom/Makefile.head variable:

	CUSTOM_H_SRC= u_curds.h otherfile.h

    Your u_curds.c file MUST be added to the CUSTOM_SRC custom/Makefile.head
    variable:

	CUSTOM_SRC= c_argv.c c_devnull.c c_help.c c_sysinfo.c u_curds.c

    and so must the associated .o file:

	CUSTOM_OBJ= c_argv.o c_devnull.o c_help.o c_sysinfo.o u_curds.o


Step 8: Compile and link in your code

    If your calc was not previously setup to compile custom code,
    you should set it up now.  The upper level Makefile (and
    the custom Makefile) should have the following Makefile
    variable defined:

	ALLOW_CUSTOM= -DCUSTOM

    It is recommended that you build your code from the top level
    Makefile.  It saves having to sync the other Makefile values.
    To try and build the new libcustcalc.a that contains u_curds.c:

	(cd ..; make custom/libcustcalc.a)

    Fix any compile and syntax errors as needed.  :-)

    Once libcustcalc.a successfully builds, compile calc:

	cd ..
	make calc

    And check to be sure that the regression test suite still
    works without errors:

	make check


Step 9: Add the Make dependency tools

    You should probably add the dependency lines to the bottom of
    the Makefile.  Given the required include files, you will at least
    have the following entries placed at the bottom of the Makefile:

	u_curds.o: ../alloc.h
	u_curds.o: ../block.h
	u_curds.o: ../byteswap.h
	u_curds.o: ../calcerr.h
	u_curds.o: ../cmath.h
	u_curds.o: ../config.h
	u_curds.o: ../endian_calc.h
	u_curds.o: ../hash.h
	u_curds.o: ../have_const.h
	u_curds.o: ../have_malloc.h
	u_curds.o: ../have_newstr.h
	u_curds.o: ../have_stdlib.h
	u_curds.o: ../have_string.h
	u_curds.o: ../longbits.h
	u_curds.o: ../nametype.h
	u_curds.o: ../qmath.h
	u_curds.o: ../shs.h
	u_curds.o: ../value.h
	u_curds.o: ../zmath.h
	u_curds.o: u_curds.c
	u_curds.o: ../custom.h

    If you have the makedepend tool from the X11 development environment
    (by Todd Brunhoff, Tektronix, Inc. and MIT Project Athena), you can
    use the following to update your dependencies:

	# cd to the top level calc directory if you are not there already

	rm -f Makefile.bak custom/Makefile.bak
	make depend

	diff -c Makefile.bak Makefile			# look at the changes
	diff -c custom/Makefile.bak custom/Makefile	# look at the changes

	rm -f Makefile.bak custom/Makefile.bak		# cleanup

Step 10: Test

    Now that you have built calc with your new custom function, test it:

	./calc -C		# run the new calc with the -C arg

    And then try out our test suite:

	C-style arbitrary precision calculator (version 2.10.3t5.1)
	[Type "exit" to exit, or "help" for help.]

	> read custom/curds.cal
	curds(a, b, [c, d, e]) defined

	> custom("curds", 2, 3, 4)


Step 11: Install

    Once you are satisfied that everything works, install the new code:

	# cd to the top level calc directory if you are not there already

	make install

    Although calc does not run setuid, you may need to be root to install
    the directories into which calc installs may be write protected.


Step 12: Contribute

    Your custom function may be of interest to some people and/or
    serve as an example of what one can do with custom functions.

    Read the file:

	help/contrib		(or run:  calc help contrib)

    and consider submitting your custom function for possible
    inclusion in later versions of calc.

## Copyright (C) 1999-2007,2021  Landon Curt Noll
##
## Calc is open software; you can redistribute it and/or modify it under
## the terms of the version 2.1 of the GNU Lesser General Public License
## as published by the Free Software Foundation.
##
## Calc is distributed in the hope that it will be useful, but WITHOUT
## ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
## or FITNESS FOR A PARTICULAR PURPOSE.	 See the GNU Lesser General
## Public License for more details.
##
## A copy of version 2.1 of the GNU Lesser General Public License is
## distributed with calc under the filename COPYING-LGPL.  You should have
## received a copy with calc; if not, write to Free Software Foundation, Inc.
## 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
##
## Under source code control:	1997/03/10 03:03:21
## File existed as early as:	1997
##
## chongo <was here> /\oo/\	http://www.isthe.com/chongo/
## Share and enjoy!  :-)	http://www.isthe.com/chongo/tech/comp/calc/
