Spires allows functions in only certain commands. The BNF is what controls this selectivity. These are the commands:
IF, LET, WHILE, UNTIL, EVAL, SHOW EVAL, SET TCOMMENT
These same commands may occur in protocols, either compiled or uncompiled. An uncompiled protocol is nothing more than a series of commands contained in an active file or the type-XEQ element of a Spires record. Type-XEQ has the same stored structure as an input active file. Therefore, either way Spires gets your protocol, the executed code looks the same.
UPROC of USERPROC and Formats are another place where functions can occur. The same basic set of commands is involved, with SHOW EVAL replaced by SET VALUE.
Functions are recognized by their starting $ character, a name and then an open parenthesis. The exception is $ZAP, which is used stand-alone in LET.
LET variable = $ZAP
All other $functions have a parenthesized parameter list, with one or more parameters. The number of parameters is determined by where the function's name is found in the element-list of the .$2FUN record-type of the $DATA file. In other words, functions are organized into structures within that record-type, and which structure they are in determines the number of parameters:
Record .$2FUN of GG.SPI.DATA
$0 Functions with variable number of arguments. $1 Functions of 1 argument $2 Functions of 2 arguments $3 Functions of 3 arguments $4 Functions of 4 arguments $5 Functions of 1 argument $6 Functions of 2 arguments $8 Functions with variable number of arguments in RECDEF ($FUN.name)
The list above shows the structure names. There can not be a $7. Spires limits each structure to 31 names. That's why $1 and $5 are both for 1 argument functions. $5 is overflow for $1. Likewise for $2 and $6. The Structure/Element number is what identifies each function. The reason for the limits goes way back to the original implementation, in which structure/element numbers get compressed down to a single byte. That limited us to about 255 PL360 functions. There can't be a Hex-00 code.
The relation between the structure/element numbers and the final one-byte code for each function is as follows:
$1 -> 01 thru 1F $2 -> 21 thru 3F $3 -> 41 thru 5F $4 -> 61 thru 7F $5 -> 81 thru 9F $6 -> A1 thru BF $0 -> C0 thru FF (this is a special case, up to 63 functions)
$8 -> $SYSXEQ with the given function name as the 1st parameter. There could be as many as 127 functions in the $8 structure.
$0 names the functions with one or more (indefinite) arguments. $MATCH is an example. $8 names functions that are found as compiled RECDEF records with ID = $FUN.name as their key. These functions are implemented using USERPROC = PROD and USERPROC = TEST, and it is the UPROC statements that do the work. UPROC = SET VALUE ... returns the answer for these functions. Actually, $8 function names are just a shortcut to doing a $SYSXEQ function.
You can see a complete list of system functions by doing the following:
-> select system functions -> show elements
Yes, it is that simple. The SYSTEM FUNCTIONS subfile is .$2FUN of $DATA. By the way, record-names that begin with a dot are pseudo-record-types. If any records are added to such record-types, they exist in the DEFQ only.
SPIRES Functions are implemented as follows. Start by determining how many parameters it will have, and if it will be implemented in PL360 code or in RECDEF records. You have to SELECT FILEDEF, TRANSFER $DATA, and add your new $function name at the end of one of the structures in .$2FUN's record definition. You can add alternate spelling for your function using the ALIAS statement. You then update the file definition and recompile it, and then dump characteristics:
-> master -> select filedef -> transfer $DATA -> ..edit <--- you add your function name as described below. -> update -> /recompile $key -> select system functions -> dump char to FUNS.OBJ rep entry FUNSTABS build
For RECDEF records, $8 is the structure which will receive the function name. You'll code the function by writing a RECDEF record with ID = $FUN.name; Look at existing $FUN.name records in RECDEF to get a flavor of how they are coded. They always have a USERPROC = PROD; and USERPROC = TEST; and most of the time TEST simply calls upon PROD to do the work. You could have separate implementations if you want to test changes. In that case you'd also have to issue the SET TMACROS command to tell Spires to use the TEST code, not PROD.
For PL360 code, you'll append the name to $0, $3, $4, $5, or $6. $1 and $2 are both full. $0 is for variable number argument functions, although many functions in $0 are almost always referenced with a fixed number of arguments.
The PL360 code is in PARSEM.TXT, in several Global Procedures all with names that begin with APPLY, such as APPLY1HI or APPLY2, etc. Each procedure has a GOTO jump list that branches to the specific code associated with each of the functions named by the related structure(s). So APPLY2 and APPLY2HI are dealing with functions from $2 and $6.
When I added the $DENCODE function, I added "ELEM = DENCODE;" to the end of the $6 structure because this is a two-parameter function. I then added new PL360 code to APPLY2HI, just after the current GOTO jump list, and I had to add another jump to the list to branch around this new code. I also had to update an EQUATE value that specifies the currently known limit for functions with two parameters. There is a warning message in the code about that detail. This is what the PL360 code looks like:
GOTO FUN239; |-- $LSTRIP --| GOTO FUN240; |-- $RSTRIP --| |-- **** Be sure to update APPLY2MX limit **** --|
FUN241: |-- $DENCODE --| GET2CHAR; STM(R1,R2,B13); R1 := R9; R2 := R10; IF R1 <= 0 THEN GOTO EXITNULL; LM(R9,R10,B13); REDUCE(R9); R3 := 7; IF R9 > R3 THEN R9 := R3; R4 := R1; R3 := PARSEND; |-- Get answer space --| R1 := R1 + R3 - NXTSPACE; IF > THEN GETCORE; IF R10 = 0 THEN R9 := DEC R10; B13(0/8) := 2.78281L; IF R9 >= 0 THEN EX(R9,XC(0,C13,C10)); |-- Key --| BEGIN PROCEDURE CYPHER (R14); BEGIN B13 := R1 OR #A5A5A5A5; UNPK(0,0,C13,C13); UNPK(0,0,C13(3),C13(3)); XC(1,C13(2),C13); XC(0,C13(1),C13(3)); END; LM(R9,R10,B13); R1 := R9; CYPHER; B13(4) := B13; R1 := R10; CYPHER; |-- Values ready --| END; R1 := R3; R10 := R3; R9 := R4; R6 := R2; R14 := R4++R4; REDUCE(R14); WHILE R4 > 0 DO BEGIN R3 := B13(4) * R14 =: B13(4) =: B13(8); R3 := B13 * R14 =: B13 XOR B13(8) =: B13(8); B1 := B6 XOR B13(8); R1 := @B1(4); R6 := @B6(4); R4 := R4 - 4S; END; PARSEND := R1; RETCORE; R1 := R9; R2 := R10; GOTO EXITSTR;
FUN240: |-- $RSTRIP --|
What was added was the GOTO FUN240; line, and all the FUN241 code. What existed before looked something like this:
GOTO FUN239; |-- $LSTRIP --| |-- **** Be sure to update APPLY2MX limit **** --|
FUN240: |-- $RSTRIP --|
Of course the real trick here is to come up with the PL360 code to implement your function. In this DENCODE example, I found code in other parts of the system that implemented the $ENCRYPT(NUM) proc, and the SET CRYPT command. These were combined to create DENCODE, and the result is stored in PARSEND (extended as needed). Every function then returns a length in R1, a value or address in R2, and a type-code in R3. In this case, EXITSTR supplies the type.
Functions can return different types. EXITNULL returns a null. There are also INT, HEX, PACK, FLAG, REAL, LINE, CHAR and STRING types. CHAR is a fixed-length string, which may be blank padded. There are special types infrequently used. They involve TRIPLES.
SPIRES Functions are installed into SPIRES/SPIBILD as follows. You've already created the FUNS.OBJ when you dumped characteristics from .$2FUN of the $DATA file. If you created your function with a RECDEF named $FUN.name, then you must compile that RECDEF to create a RECHAR record. If you created your function with PL360 code (PARSEM.TXT modifications), then you must compile PARSEM.TXT to get all remaining object decks.
In SPIRES, you should do the following:
-> set xeq spiproto -> ..compall pl360.parsem
This should generate all the object decks associated with PARSEM.TXT.
You must now move your FUNS.OBJ, from where it was stored when it was created by the dump char command, into the "obj" directory used for linking new versions of SPIRES/SPIBILD. An old version of FUNS.OBJ should already be in your spisrc/obj directory. Your new FUNS.OBJ should be in the spisys directory, the same place all GG.SPI files are located. Replace the old with the new as follows (Mac OSX example):
$ cd $ mv spisys/FUNS.OBJ spisrc/obj/.
In SPIRES, you should now do the following:
-> set xeq spiproto -> ..emlink
This should move any object decks created by compiling PARSEM.TXT, and then link all versions of SPIRES and SPIBILD. The new versions will be in the spisrc/obj directory.
WARNING: Be sure you know where things are located. GG.SPI files are along some path that usually leads to spisys. GQ.DOC files are SPIKE files, and are usually some other place. MA.INT is where "spisrc/pl360" and "spisrc/obj" are located, although these paths can all be differently named.
Be sure your .empath file defines /paths for these accounts. Also be sure you have "link...." files in spisrc/obj, such as "link.spiresx". You need link files for: spibildh, spibildx, spiresh, spiresx.
And be sure you have "compall" and "emlink" in spiproto.
You must document your function. There are several places where such documentation exists. First is within .$2FUN of the $DATA file. Second is within PARSEM.TXT or a RECDEF/RECHAR with $FUN.name.
You should update the FILEDEF.BI text file in spisrc/bi to reflect the ELEM (and ALIAS) statements you added to the $DATA file. FILEDEF.BI is a seed file used to build $DATA from scratch.
You should then create a new record in the PROTOCOLS MANUAL subfile. This is a SPIKE-file, and is easiest to work with using the ..spike protocol defined in both SPIPROTO and SPIKE PROTOCOLS. If you have SET XEQ to either SPIPROTO or SPIKE PROTOCOLS, then you can use ..spike to select a SPIKE-file. There is a SPIKE.INDEX in the GQ.DOC directory that lists all the SPIKE-files accessible using the ..spike protocol. For our purposes, this is enough:
-> set xeq spike protocols -> ..spike proto
All currently defined functions are documented in this SPIKE-file, which has the PROTOCOLS MANUAL subfile name. You see this when you EXPLAIN any existing function. The header of the explanation indicates which section of the PROTOCOLS MANUAL contains the documentation.
You should use the WRITE format to alter SPIKE-file records. You can TRANSFER an existing section to get an idea of how they are constructed. In fact, you can use an existing record as a model for a new record. Be sure to come up with a unique section number.
The -XP and -KT lines define the explainable terms. Usually, the first -XP phrase is the key phrase. All aliases follow, with semi-colon separations.
Once you have created a new section in the PROTOCOLS MANUAL documenting your function, you must cause the EXPLAIN subfile to get updated. This is done with the ..spiexp protocol, which takes the same argument as the ..spike protocol.
-> set xeq spike protocols -> ..spikexp proto
At this point you create a result or stack of sections to be output to the EXPLAIN subfile, or use Global-For to create a display of records.
-> ..spikexp proto :System (ORVYL, UNIX or CMS)? :Job (EXPLAIN, SYNTAX, EXAMPLE)? * PROTOCOLS MANUAL * Establish a Global-FOR, and in active display. * or obtain a RESULT or STACK , and OUTPUT * Then ------- * ..upd.exp * which does: SELECT EXPLAIN * SET FORMAT EXPLAIN UPDATE * INPUT ADDupd * To publish, ..orvyl.html -> stack 7.3.2a -Stack: 1 RECORD -> output clr -> ..upd.exp -? SELECT EXPLAIN -? SET FORMAT EXPLAIN UPDATE -? INPUT ADDUPD - ADD @Line 40. Key = $DENCODE FUNCTION - Requests/Success: ADD 1 1 SUM 1 1 -End of batch input ->
Once you've created SPIRES/SPIBILD by linking FUNS.OBJ and any object decks associated with functions defined by PL360 code in PARSEM.TXT, you have a complete implementation. You simply copy all versions of SPIRES/SPIBILD to other sites. If you used RECDEF/RECHAR to create $FUN.name functions, you must copy the RECDEF (source) to other sites and compile them there.
You do NOT have to migrate the $DATA file definition unless you want the other site(s) to have a $DATA file. If no one is going to SELECT SYSTEM FUNCTIONS, you don't need $DATA. Of course you need it at the site where you created the function.
You should also copy the explanations added or updated in the EXPLAIN subfile. The best way to do that is by creating a LOAD file from the DEFQ of the EXPLAIN subfile after doing the ..upd.exp protocol within the ..spikexp process. You then INPUT LOAD loadfile UPDATE at each site that has an EXPLAIN subfile.