How to write new "built-in" functions for LPMOO =============================================== The addition of new built-in functions is discouraged. Instead, consider writing an LPC module to be called with the existing MOO->LPC mechanism. See the file ./calling-lpc for more details. Adding new built-in functions (to be available to programmers) is fairly simple. It requires programming the new functionality in LPC, which should not be difficult for any seasoned C or MOO programmer. To add a new function, edit /std/bfuns/extrafuns.c (note that the `mudlib' directory in the LPMOO distribution is always treated as the root). Begin writing the function like this: # ifdef FUNCDEF FUNCDEF(0, "function_name", minargs, maxargs) # else /* * NAME: bfun->function_name() * DESCRIPTION: what this function does */ varargs MOOVAL b_function_name(mixed *info, MOOVAL arg1, MOOVAL etc...) { ... return value; } # endif Replace `minargs' and `maxargs' with the number of arguments this function expects to receive. You may use -1 for `maxargs' to indicate only a minimum. If your function will accept a variable number of arguments, you should declare the function exactly as above (with MOOVAL etc... to represent all optional arguments). Otherwise, you should name all arguments explicitly. If you declare optional arguments as above, the extra arguments actually passed to your function will be accumulated into the array `etc', so to access them you would reference etc[0], etc[1], and so forth. The number of extra arguments can be found with sizeof(etc). Before using any incoming arguments, you should assert their types: ASSERT(arg, type); where `arg' is the argument to test, and `type' is one of NUM, STR, OBJ, ERR, LST, FLT, or TBL. This will cause an error to be raised if the indicated argument is not of the indicated type. If an argument can be of any type, you can determine the type with TYPEOF(arg). This yields an integer value, which you can match with the macros T_NUM, T_STR, T_OBJ, and so forth. If you just want to know if a value is of a specific type, you can test instead with the macros STRP(), NUMP(), OBJP(), and so forth. (The latter method is much more efficient than using TYPEOF().) To access the value of an argument, you must first already have determined what type the argument is. You can then access the value as follows: Expression Resulting LPC Type ========== ================== NUMVAL(x) int STRVAL(x) string OBJVAL(x) int ERRVAL(x) int LSTVAL(x) MOOVAL * FLTVAL(x) float TBLVAL(x) mapping BUFVAL(x) string You can declare LPC variables in your function according to this table. You can also declare a variable of type `MOOVAL' to hold any arbitrary MOO value. (The * suffix above for lists denotes an LPC array of arbitrary MOO values.) There is an additional type used internally by LPMOO to indicate various status conditions. The type is T_STW, and you can test for it with STWP(x). MOO objects are represented in LPC by DGD objects. The LPC datatype is `object', but to translate an object argument (which is represented by an integer) into a DGD object, you must do the following: object ob; ... GET_VALID_OBJ(ob, OBJVAL(arg)); `ob' will now be a "pointer" to the appropriate DGD object, or else an error will have been raised if the object was not valid. MOO tables have a particular structure in LPC that must be adhered to; the following macros are used to manipulate tables: TNEW() returns a new, empty table TLOOKUP(table, key) returns the value associated with `key' in `table', or STW(0) if `key' is not bound TINSERT(table, key, value) inserts {key ~ value} into `table', and returns a pointer to a two-element array in the table containing ({ key, value }) TDELETE(table, key) deletes `key' and its associated value from `table' (returns nothing) TMERGE(table1, table2) merges all key/value pairs from `table2' into `table1' TCOMPARE(table1, table2) returns 1 iff `table1' and `table2' contain precisely the same key/value pairs TKEYS(table) returns the keys in `table' as an array TVALUES(table) returns the values in `table' as an array These functions will mutate the given table argument; to preserve immutability, you must make a copy of the table before passing them to the macro. For example: TDELETE(table = table + TNEW(), STR("foo")) Your function must return a value before completing. Values should be packaged back from their LPC representation into the various MOO types: Expression and Arg Example MOO Result ================== =========================== =========== NUM(int) NUM(-24) -24 STR(string) STR("something") "something" OBJ(int) OBJ(99) #99 ERR(int) ERR(E_RANGE) E_RANGE LST(MOOVAL *) LST( ({ NUM(3), OBJ(7) }) ) {3, #7} FLT(float) FLT(12.34) 12.34 TBL(mapping) TBL(TNEW()) {~} BUF(string) BUF("Abc\n") [65, 98, 99, 10] To return a value, use: return value; If at any time you need to raise an error, use this: return RAISE(error); where `error' is one of the standard MOO errors, E_PERM, E_TYPE, and so forth. Note this is different from simply returning an error value! You may wish to examine the file /std/bfuns/moofuns.c for examples of how all of the LambdaMOO builtins were implemented. Once you are finished making additions to /std/bfuns/extrafuns.c, you must recompile DGD to incorporate those changes. The LPC code will be translated into C and linked with the rest of the server. To bring up your database with the new version of the server, you must first make a text dump with dump_database(1) and bootstrap from the resulting db file. (Instructions for this are in the `checkpoints' doc file.) Note that introducing dependencies on special builtin functions in your database means that you will be unable to use your database with a server that does not implement these builtins! This is why adding new builtins is discouraged, especially if the same functionality can be written in LPC and made accessible through the MOO->LPC interface.