/* EDX_CALLUSERC.C - externcal CALL_USER routines for EDX. This module is used in conjunction with the EDX editor. This module is intended to be compatible with both VAX/VMS VAX C and Alpha AXP/OpenVMS DEC C */ #if 0 ASSUME - THIS MODULE ASSUMES THE FOLLOWING: char *pointer; int integer32; char *pointer; sizeof(pointer) = longword (4 bytes, 32 bits) int i; sizeof(i) = longword (4 bytes, 32 bits) long int j; sizeof(j) = longword (4 bytes, 32 bits) short int k; sizeof(k) = word (2 bytes, 16 bits) This module is called using the TPU command: OUTSTR := CALL_USER( CODE, INSTR); psudo-c call: status = tpu$calluser( code, instr, outstr ); CODE - Integer. Input (read only), passed by reference. INPUT REQUEST CODE: The input request code is split into words. The high word indicates the general category of the request, the low word indicates the specific request within the category. This simplifies parsing. INSTR - Input string, read only, passed by descriptor. 8(AP) = address of string descriptor. The string is passed by a fixed length descriptor of the form: ----------------------------------------- | class | dtype | string length | ----------------------------------------- | address | ----------------------------------------- length = nn (length of string) dtype = 14 (character string DSC$K_DTYPE_T) class = 0 (DSC$K_CLASS_Z unspecified. This doesn't work for some things) address = xx (address of first byte of string) Because DSC$K_CLASS_Z doesn't work for some library calls, we create our own string descriptor. The length and address fields are copied over, dtype is set to DSC$K_DTYPE_T and class is set to DSC$K_CLASS_S. OUTSTR - Output string, write only, by descriptor. The string is passed by a dynamic string descriptor where: length = 0 (length of string) dtype = 14 (character string DSC$K_DTYPE_T) class = 2 (dynamic string descriptor DSC$K_CLASS_D) address = 0 (invalid address) Because the result string is dynamically allocated we use the system service run-time library routine STR$COPY_DX to return the string. The first 9 characters of the output string are reserved for RETCODE, the return status code number. The calling program strips the first 9 characters off the string and converts it to a return status code number using the INT() function. RETURN STATUS The return value of this function tpu$calluser. This module always returns a SS$_NORMAL status when it returns. The only way an error status is returned is if an unexpected nasty error happens. If an error status is returned TPU will take the ON_ERROR - ENDON_ERROR action if one exists in the calling procedure. ; INPUT ITEM CODE CATEGORIES: ; ^x0001xxxx - SYSTEM (65536) ; ^x00010001 - LOCK FILE (65537) ; INSTR = filename ; ; ^x00010002 - UNLOCK FILE (65538) ; INSTR = filename ; ; ^x00010003 - SHOW LOGICAL (65539) ; INSTR = logical name to translate ; OUTSTR = logical name translation ; ; ^x00010004 - SHOW SYMBOL (65540) ; INSTR = DCL symbol to translate ; OUTSTR = symbol translation ; ; ^x00010005 - PRINT ERROR MESSAGE (65541) (NO LONGER USED) ; INSTR = string containing error number (in decimal) ; ; ^x00010006 - CHECK IF FILE IS LOCKED (65542) ; INSTR = string containing filename to check ; Return INCODE = (1 = file locked, 0 file not locked) ; ; ^x00010007 - SET DEFAULT DIRECTORY (65543) ; INSTR = string containing new directory to go to. ; ; ^x00010008 - DEFINE LOGICAL NAME (65544) ; INSTR = string containing logical name followed by translation. ; The string is of the form "log-nam value", where "log-nam" ; is the logical name to be defined and "value" is the ; value to be assigned to the logical name. One or more ; spaces must separate the two. ; ; ^x00010009 - SHOW IDENT NUMBER (65545) ; OUTSTR = Ident version number. ; ; ^x0001000A - DELETE FILE (65546) ; INSTR = Filename to delete. ; ; ^x0001000B - SET SYMBOL (65547) ; INSTR = symbol to set ; ; ^x0001000C - CALC (65548) (currently not used) ; INSTR = math expression ; ; ; ^x0002000n - SENDING MESSAGE FLAGS (131072) ; n - Value of message flags setting ; This code is used when we recursively call ourselves to ; give ourselves the current value (0-15) of message flags. ; Used when a message is signaled. ; ; ; ^x0003000n - DIRECTORY (196608) ; n - Code used for reentry. ; ; ; ^x00040001 - TRANSLATE FROM EBCDIC TO ASCII (262145) ; INSTR = EBCDIC string ; OUTSTR = ASCII string ; ; ^x00040002 - TRANSLATE FROM ASCII TO EBCDIC (262146) ; INSTR = ASCII string ; OUTSTR = EBCDIC string ; ; ^x00040003 - INITIALIZE RANDOM NUMBER GENERATOR WITH PASSWORD (262147) (NOT USED) ; INSTR = Password ; OUTSTR = Status ; ; ^x00040004 - ENCRYPT STRING (262148) (NOT USED) ; INSTR = String to encrypt ; OUTSTR = Encrypted string ; ; ^x00040005 - DECRYPT STRING (262149) (NOT USED) ; INSTR = String to decrypt ; OUTSTR = Decrypted string ; ; ^x0005000n - SORT (327680) ; n = 1. Preparse command line ; 2. Pass files and do sort (for file sort) ; 3. Postparse command line ; 4. Pass a record to sort. (Repeat until all records passed) ; 5. Do record sort ; 6. Receive a record in sorted order. (Repeat until all records received) ; 7. Cleanup record sort ; ; ^x0006000n - SPELL (393216) ; n = 1. Dictionary browse previous page ; n = 2. Dictionary browse using word ; n = 3. Dictionary browse next page ; n = 4. Spell textline ; n = 5. Spell guess ; n = 6. Accept word (add to accepted word list) ; n = 7. Add word to personal dictionary. ; n = 8. Dump commonwords list ; n = 9. Save misspelled word and its correction ; ; ^x0007000n - LIBRARY (458752) ; n = 1. Initialize, open for read, lookup_key ; n = 2. Return next line of text from module ; n = 3. Close text library ; n = 4. Initialize, open for write, lookup_key ; n = 5. Write next line of text to module ; n = 6. Write end-of-module record ; ;#if DEBUG ; ^x1001000n - HANDLER_TEST (268500992) ; n = 1. Generate access violation ; n = 2. Generate divide by zero exception ; n = 3. Return TPU$GL_VERSION ; n = 4. Return TPU$GL_UPDATE ; n = 5. signal EDX__UNKNCODE ;#endif ; ; System routines ; TPU$CALLUSER !Main entry point. Entered via TPU CALL_USER instruction. ; SHOW_ID !Show ident number ; FMTOUTSTR !Format output string ; EDX_SIGNAL !Signal message ; HANDLER !Error handler ; ; Display directory listing ; EDX_DIRECTORY !Display directory listing ; GETDEFDIRFLGS ! support for directory command ; ;Sort Routines: ; EDX_SORT !Main entry. ; SORT_PREPARSE !Preparse SORT command ; SORT_PASSFILES !Pass filenames for file sort ; SORT_POSTPARSE !Finish parsing SORT command ; SORT_DO_FILE !Do file sort ; SORT_RELEASE_REC !Give record to sort when using record sort ; SORT_RETURN_REC !Get record from sort when using record sort ; ; Spelling checker and dictionary ; EDX_SPELL !Spelling dictionary main entry ; SPELL_INIT !Initialize spelling checker ; SPELL_TEXTLINE !Spell check a line of text ; DIC_LOOKUP_WORD !Look up a word in the dictionary ; DIC_BROWSE !Browse through the dictionary ; DIC_BROWSE_PREV_PAGE ! support for browse ; DIC_BROWSE_WORD ! support for browse ; DIC_BROWSE_FILL ! support for browse ; SPELL_GUESS ! Guess the spelling of a word. From Vassar. ; SPELL_ACCEPT_WORD ! Insert word into accepted word tree list ; SAVE_CORRECTION ! Save misspelled word and its correction ; PRINT_ACCEPT_NODE ! debug routine for accepted tree list ; PRINT_SAVCOR_NODE ! debug routine for accepted tree list ; TRAVERSE_ACCEPT_TREE ! debug routine for accepted tree list ; TRAVERSE_SAVCOR_TREE ! debug routine for saved corrections tree list ; ALLOCATE_NODE ! support routine for accepted tree list ; COMPARE_NODE ! support routine for accepted tree list ; SPELL_PERSDIC_ADD ! add word to personal dictionary ; DUMP_COMMONWORDS ! dump the commonword list ; ; Lock and unlock files ; LOCK_FILE !Lock a file preventing others from editing it ; UNLOCK_FILE !Unlock file ; EDX_CKFILK !Check if file is locked ; SRCH_LNKFABLST !Search our list of locked files ; EDX_PARSE !Parse a filename ; ; Miscellaneous ; EDX_SETDEF !Change users default directory ; SET_LOGICAL !Create a logical name ; SET_SYMBOL !Create a DCL symbol ; SHOW_LOGICAL !Show translation of a logical name ; SHOW_SYMBOL !Show translation of a DCL symbol ; DELETE_FILE !Delete a file ; TRA_EBC_ASC !Translate EBCDIC to ASCII ; TRA_ASC_EBC !Translate ASCII to EBCDIC ; ; Text libraries ; LIBRARIAN ; LBR_INIT ; LBR_CLOSE ; LBR_READNEXT ; LBR_WRITENEXT ; LBR_REPLACE ; ; David Deley May, 1988 Original ; David Deley Nov. 1988 New version compatible with VMS 5.0 ; David Deley Nov. 1989 v5.7 with string sort ; David Deley Mar. 1990 V6.0 with spelling checker dictionary ; David Deley Sep. 1990 V7.2 more spelling checker and fixes for VMS 5.3 bug ; David Deley Dec. 1991 V8.1 DES encryption removed. ; David Deley Feb. 1992 V8.2 Spell check 'Accept' could fail with access violation in ALLOCATE_NODE (caught by HANDLER) ; David Deley Nov. 1992 V8.4 Sort default key length 132 -> 65535 ; Properly signal SOR$_BAD_LRL condition ; David Deley Mar. 1993 V8.6 No changes. ; David Deley Jul. 1993 V9.0 (474) Converted to C. ; David Deley Nov. 1993 V9.0 (475) Fix add_persdic() invalid RAB C programming notes: char outline subr( outline ); PASSED BY REFERENCE subr( outline[0] ); PASSED BY VALUE subr( &outline[0] ); PASSED BY REFERENCE ("string") PASSED BY REFERENCE != takes precedence over & so use parentheses ~ - Bitwise .NOT. (Ones Complement) ! - Logical .NOT. (TRUE/FALSE) && - Logical AND & - Bitwise .AND. ptr = (char *)(node+12); Take the value of 'node', add 12, claim the result is a pointer to a stream of characters, set 'ptr', a variable of type "pointer to a stream of characters" to that value. end = ptr + *(unsigned short *)(node+10); Take the value of 'node', add 10, claim the result is a pointer to an unsigned short word. Take the value of that unsigned short word, add it to 'ptr', place in 'end'. char *fab$l_fna; * defined as "pointer to char" * (*NO*) lexfab.fab$l_fna = &"WORDLIST:"; * DEC C complains "pointer to array of char " incompatible * (YES) lexfab.fab$l_fna = (char *) "WORDLIST:"; * DEC C accepts * char wdbuf[80]; char *rab$l_ubf; * defined as "pointer to char" * (*NO*) cwrab.rab$l_ubf = &wdbuf; * DEC C complains * (YES) cwrab.rab$l_ubf = (char *) wdbuf; * DEC C accepts * char (*ctable)[]; * pointer to an array of char * Fields to watch for above error: FAB: char *fab$l_xab;??? * xab address * char *fab$l_fna; * file name string address * char *fab$l_dna; * default file name string addr * RAB: char *rab$l_ubf; * user buffer address * char *rab$l_rbf; * record buffer address * char *rab$l_rhb; * record header buffer addr * char *rab$l_kbf; * key buffer address * char *rab$l_pbf; * prompt buffer addr * char *rab$l_xab;??? * XAB address * NAM: char *nam$l_rsa; * resultant string area address * char *nam$l_esa; * expanded string area address * char *nam$l_node; * Nodespec address * char *nam$l_dev; * Device address * char *nam$l_dir; * Directory address * char *nam$l_name; * Filename address * char *nam$l_type; * Filetype address * char *nam$l_ver; * Version number address * XAB: char *xab$l_nxt;??? * Next XAB in chain * ALL global variables declared outside of a routine must be 'static' otherwise they end up in a psect of type SHAREABLE and WRITABLE and we get the error: SYSTEM-F-NOTINSTALL, writable shareable images must be installed const constants are not constant unless they are also static. When DICID was declared const without static we got DICHEADERR /*---------------------------------------------------------------------------*/ /* A REVIEW OF C POINTERS */ #include rms struct locked_file { struct locked_file *next; struct FAB fab; struct NAM nam; char expanded_file_name[NAM$C_MAXRSS]; char resultant_file_name[NAM$C_MAXRSS]; }; static struct locked_file *locked_files_head = 0; /* head of the singulary linked list */ static struct locked_file blk; int srch( struct locked_file **prelnkptradr ) /* pointer to a pointer to a */ { /* structure of type locked_file */ int a,b,c,d; a = prelnkptradr; /* address of prelnkptr. (value of prelnkptradr) */ b = *prelnkptradr; /* value of prelnkptr. (address of blk) */ c = (*prelnkptradr)->next; /* c = blk.next */ d = &prelnkptradr; /* address of {address of prelnkptr} in call frame argument list. (SHOW SYMBOL/ADDRESS prelnkptradr). */ } main() { int i,j,k; struct locked_file *prelnkptr; prelnkptr = &blk; i = prelnkptr; /* value of prelnkptr. (address of blk) */ prelnkptr->next = 0x1234; /* blk.next = 0x1234 */ j = prelnkptr->next; /* j = blk.next */ k = &prelnkptr; /* address of variable prelnkptr */ srch( &prelnkptr ); /* pass by reference */ } Update notes: David Deley 06-OCT-1993 (469) Fix sepll_guscon David Deley 22-NOV-1993 (477) Fix sort postparse change value_len to short #endif /*===========================================================================*/ #include <chfdef.h> /* Include CHF$ definitions (chfdef2.h for ALPHA?) */ #include <climsgdef.h> #include <ctype.h> /* _toupper macro */ #include <descrip.h> /* Define DSC$ and descriptor structures */ #include <jpidef.h> /* Include JPI$ definitions */ #include <lbrdef.h> /* Include LBR$ definitions */ #include <libdef.h> /* Include LIB$ definitions */ #include <lib$routines.h> /* Prototype all LIB$ routines */ #include <lnmdef.h> /* Include LNM$ definitions */ #include <rms.h> /* Include all FAB$, NAM$, XAB$, etc... */ #include <secdef.h> /* Include SEC$ definitions */ #include <signal.h> /* Define VAXC$ESTABLISH */ #include <sor$routines.h> /* Define SOR$ routines */ #include <sordef.h> /* Define SOR$_ error messages */ #include <ssdef.h> /* Include SS$ system condition code definitions */ #include <starlet.h> /* Prototype all SYS$ routines */ #include <stdio.h> /* Define TRUE, FALSE */ #include <stdlib.h> /* Standard library */ #include <string.h> /* prototype memcpy, memset */ #include <stsdef.h> /* Include STS$ status value codes */ /* EXTERNAL GLOBAL VARIABLES */ globalref TPU$GL_VERSION; globalref TPU$GL_UPDATE; globalref edx_commands; /* SYMBOLS WITH NO *.H FILE (These symbols are resolved by the linker) */ globalvalue SOR$M_NOSIGNAL; /* defined in SYS$LIBRARY:SORTSHR.EXE */ globalvalue SOR$M_STABLE; /* defined in SYS$LIBRARY:SORTSHR.EXE */ globalvalue SOR$M_EBCDIC; /* defined in SYS$LIBRARY:SORTSHR.EXE */ globalvalue SOR$M_MULTI; /* defined in SYS$LIBRARY:SORTSHR.EXE */ globalvalue SOR$M_NODUPS; /* defined in SYS$LIBRARY:SORTSHR.EXE */ globalvalue LBR$_KEYNOTFND; /* defined in SYS$LIBRARY:LBRSHR.EXE */ /* EDX STATUS CODES (Defined in EDX_MESSAGES.MSG) */ globalvalue EDX__ALK; globalvalue EDX__CREPERSDIC; globalvalue EDX__DBFDPTRLB; globalvalue EDX__DICHEADERR; globalvalue EDX__DICLOAD; globalvalue EDX__DICLOADED; globalvalue EDX__DICVERSERR; globalvalue EDX__ERRMAPDIC; globalvalue EDX__ERROPEN; globalvalue EDX__ERROPENDIC; globalvalue EDX__GUSINTERR2; globalvalue EDX__LOCKED; globalvalue EDX__MEMERR; globalvalue EDX__MODNOTFND; globalvalue EDX__NOLOCK; globalvalue EDX__NOMODNAM; globalvalue EDX__NOTLOCKED; globalvalue EDX__PERSDICERR; globalvalue EDX__SPLINITERR; globalvalue EDX__UNEXPERR; globalvalue EDX__UNKNCODE; globalvalue EDX__UNLOCKED; globalvalue EDX__WORDADD; /* INCODES: SYSTEM STUFF */ #define INCODE_SYSTEM 0x00010000 /* (65536) */ #define INCODE_LOCK_FILE 0x00010001 /* (65537) */ #define INCODE_UNLOCK_FILE 0x00010002 /* (65538) */ #define INCODE_SHOW_LOGICAL 0x00010003 /* (65539) */ #define INCODE_SHOW_SYMBOL 0x00010004 /* (65540) */ /*#define INCODE_SIGNAL_ERROR_MESSAGE 0x00010005 (65541) no longer used */ #define INCODE_CHECK_IF_FILE_IS_LOCKED 0x00010006 /* (65542) */ #define INCODE_SET_DEFAULT_DIRECTORY 0x00010007 /* (65543) */ #define INCODE_DEFINE_LOGICAL_NAME 0x00010008 /* (65544) */ #define INCODE_SHOW_IDENT_NUMBER 0x00010009 /* (65545) */ #define INCODE_DELETE_FILE 0x0001000A /* (65546) */ #define INCODE_SET_SYMBOL 0x0001000B /* (65547) */ #define INCODE_CALC 0x0001000C /* (65548) not used */ /* INCODES: SET MESSAGE_FLAGS */ #define INCODE_SENDING_MESSAGE_FLAGS 0x00020000 /* (131072) */ /* INCODES: DIRECTORY */ #define INCODE_DIRECTORY 0x00030000 /* (196608) */ /* INCODES: TRANSLATE */ #define INCODE_TRANSLATE 0x00040000 /* (262144) */ #define INCODE_TRANS_EBCDIC_TO_ASCII 0x00040001 /* (262145) */ #define INCODE_TRANS_ASCII_TO_EBCDIC 0x00040002 /* (262146) */ #define INCODE INIT_RANDOM 0x00040003 /* (262147) not used */ #define INCODE_ENCRYPT STRING 0x00040004 /* (262148) not used */ #define INCODE_DECRYPT_STRING 0x00040005 /* (262149) not used */ /* INCODES: SORT */ #define INCODE_SORT 0x00050000 /* (327680) */ #define SORT_FILE_INIT 0x00050001 /* (327681) */ #define SORT_FILE_DO 0x00050002 /* (327682) */ #define SORT_RECORDS_INIT 0x00050003 /* (327683) */ #define SORT_RECORDS_PASS 0x00050004 /* (327684) */ #define SORT_RECORDS_DO 0x00050005 /* (327685) */ #define SORT_RECORDS_RECV 0x00050006 /* (327686) */ #define SORT_FINISH 0x00050007 /* (327687) */ /* INCODES: SPELL */ #define INCODE_SPELL 0x00060000 /* (393216) */ #define INCODE_SPELL_DIC_BROWSE_PA 0x00060001 /* (393217) */ #define INCODE_SPELL_DIC_BROWSE_RW 0x00060002 /* (393218) */ #define INCODE_SPELL_DIC_BROWSE_PZ 0x00060003 /* (393219) */ #define INCODE_SPELL_TEXTLINE 0x00060004 /* (393220) */ #define INCODE_SPELL_GUESS 0x00060005 /* (393221) */ #define INCODE_SPELL_ACCEPT_WORD 0x00060006 /* (393222) */ #define INCODE_SPELL_ADD_PERSDIC 0x00060007 /* (393223) */ #define INCODE_SPELL_DUMP_COMMONWORDS 0x00060008 /* (393224) */ #define INCODE_SPELL_SAVE_CORRECTION 0x00060009 /* (393225) */ /* INCODES: LIBRARIAN */ #define INCODE_LIBRARIAN 0x00070000 /* (458752) */ #define INCODE_LIBRARIAN_OPEN_READ 0x00070001 /* (458753) */ #define INCODE_LIBRARIAN_OPEN_WRITE 0x00070002 /* (458754) */ #define INCODE_LIBRARIAN_CLOSE 0x00070003 /* (458755) */ #define INCODE_LIBRARIAN_READNEXT 0x00070004 /* (458756) */ #define INCODE_LIBRARIAN_WRITENEXT 0x00070005 /* (458757) */ #define INCODE_LIBRARIAN_REPLACE 0x00070006 /* (458758) */ #if DEBUG /* INCODES: HANDLER_TEST */ #define INCODE_HANDLER_TEST 0x10010000 /* (268500992) */ #endif /* SPECIAL SIZES */ #define int16 short int #define int32 long int /* STRUCTURES */ typedef struct { unsigned short buflen; unsigned short itmcod; unsigned int bufadr; unsigned int retadr; } item_list_3; static struct dsc$descriptor_s s_descr; struct dsc$descriptor_s *s_descrip(char *str_ptr) { s_descr.dsc$w_length = strlen(str_ptr); s_descr.dsc$b_class = DSC$K_CLASS_S; s_descr.dsc$b_dtype = DSC$K_DTYPE_T; s_descr.dsc$a_pointer = str_ptr; return &s_descr; } /* DESCRIPTOR FOR CONSTANTS */ struct dsc$descriptor_sc { const unsigned short dsc$w_length; /* length of data item in bytes */ const unsigned char dsc$b_dtype; /* data type code */ const unsigned char dsc$b_class; /* descriptor class code = DSC$K_CLASS_S */ const char *dsc$a_pointer; /* address of first byte of data storage */ }; #define $CONST_DESCRIP(name,string) const struct dsc$descriptor_sc name = { sizeof(string)-1, DSC$K_DTYPE_T, DSC$K_CLASS_S, string } struct locked_file { struct locked_file *next; struct FAB fab; struct NAM nam; char expanded_file_name[NAM$C_MAXRSS]; char resultant_file_name[NAM$C_MAXRSS]; }; static struct locked_file *locked_files_head = 0; /* head of the singulary linked list */ /* CONSTANTS */ static const volatile char IDENTF[] = { __FILE__ } ; /* Identify source code. */ #define IDENT "C version (480)" /* IDENTL */ #define SPACE 0x20 /* Ascii space character */ #define TAB 0x09 /* Ascii tab character */ #define BUFLEN 256 /* Usual length of string buffers (evenly divisible by 8 for quadword alignment) */ #define MAXLEN 960 /* Maximum length of line in buffer (evenly divisible by 8 for quadword alignment) */ #define SET_MESSAGE_FLAGS 2 /* Code for recursive call to set message flags */ #define FAILURE 0 #define SUCCESS 1 #define BELL 0x07 const static char bell[2] = { BELL, 0 }; const static $DESCRIPTOR(bell_desc,bell); /* GLOBAL VARIABLES */ static struct dsc$descriptor_d *outstr_desc_ptr; /* pointer to outstr_desc */ static long int *incode_ptr; /* pointer to incode */ static struct dsc$descriptor_s instr_desc = { 0, DSC$K_DTYPE_T, DSC$K_CLASS_S, 0 }; static int msgflgs; /* holds current message flag setting */ /* MACROS */ #define LENGTH(c) sizeof(c)-1 /* subtract off the trailing ASCIZ NULL character */ #define MIN(a,b) ( ((a) > (b)) ? (b) : (a) ) /* UTILITY ROUTINES */ /* string_compare implicitly null pads shorter string */ int string_compare(int str1len, char str1[], int str2len, char str2[]) { int i,minl; minl = MIN( str1len, str2len ); for (i = 0; i <= minl && str1[i] == str2[i]; ++i ); if (i < minl) return(str1[i] - str2[i]); else return(str1len - str2len); } /* pass symbolic values by reference */ int *byref( symbol ) { static val; val = symbol; return( &val ); } /*---------------------------------------------------------------------------*/ /* PROTOTYPES */ /* SYSTEM */ unsigned int TPU$CALLUSER( int *incode, /* Main entry point. Entered via TPU CALL_USER instruction. */ struct dsc$descriptor_d *instr_z, struct dsc$descriptor_d *outstr ); void tpu_calluser( int *incode, struct dsc$descriptor_d *instr_z_ptr, struct dsc$descriptor_d *outstr_d_ptr ); void show_id(); /* Show ident number */ void fmtoutstr( int nargs, int *sig_array ); /* Format output string */ void edx_signal( int nargs, int *sig_array ); /* Signal message */ unsigned long int handler(unsigned int *sigargs, unsigned int *mechargs ); /* Error handler */ #if DEBUG void test_handler(); /* Test error handler */ void test_handler_sub(); /* Test error handler */ #endif /* DIRECTORY */ void edx_directory(); /* Display directory listing */ unsigned long int null_handler(unsigned int *sigargs, unsigned int *mechargs); void getdefdirflgs(int *dirflgs_ptr); /* support for directory command */ /* SPELL */ void edx_spell(); /* Spelling dictionary main entry */ int spell_init(); /* Initialize spelling checker */ void spell_textline( struct dsc$descriptor_s *inbuf_desc); /* Spell check a line of text */ int dic_lookup_word(int wdlen, char * wdbeg); /* Look up a word in the dictionary */ void binsrch_maindic( int *low, int *high, char *target_word );/* Binary search the main dictionary */ void dic_browse(); /* Browse through the dictionary */ void dic_browse_prev_page( int nchars, int nrows); /* support for browse */ void dic_browse_word( int inwdlen, char *inwdbeg, int window_columns, int window_rows ); /* support for browse */ void dic_browse_fill( char *dptr, int window_columns, int window_rows ); /* support for browse */ void spell_guess(); /* Guess the spelling of a word. From Vassar. */ int spell_gusrev(); /* Guess reversals */ int spell_gusvol(); /* Guess vowels */ int spell_gusmin(); /* Guess minus */ int spell_guspls(); /* Guess plus */ int spell_guscon(); /* Guess consonants */ void spell_accept_word(); /* Insert word into accepted word tree list */ void save_correction( struct dsc$descriptor_s *str_desc ); /* Save misspelled word and its correction */ #if DEBUG void print_accept_node(); /* FOR DEBUGGING ONLY debug routine for tree lists */ void print_savcor_node(); /* FOR DEBUGGING ONLY debug routine for tree lists */ void traverse_accept_tree(); /* FOR DEBUGGING ONLY debug routine for accepted tree list */ void traverse_savcor_tree(); /* FOR DEBUGGING ONLY debug routine for saved corrections tree list */ #endif int allocate_node( struct dsc$descriptor_s *symstr_desc, char **retadr, /* support routine for accepted tree list */ struct dsc$descriptor_s *usrdat_desc ); int compare_node( struct dsc$descriptor_s *symstr_desc, char *treenode, /* support routine for accepted tree list */ struct dsc$descriptor_s *usrdat_desc ); void add_persdic(); /* add word to personal dictionary */ void dump_commonwords(); /* dump the commonword list */ /* SORT */ void edx_sort(); /* Main entry. */ void sort_preparse(); /* Preparse SORT command */ int sort_passfiles(); /* Pass filenames for file sort */ int sort_postparse(); /* Finish parsing SORT command */ void sort_do_file(); /* Do file sort */ void sort_release_rec(); /* Give record to sort when using record sort */ void sort_return_rec(); /* Get record from sort when using record sort */ /* LOCK AND UNLOCK FILES */ void lock_file(); /* Lock a file preventing others from editing it */ void unlock_file(); /* Unlock file */ void edx_ckfilk(); /* Check if file is locked */ int srch_locked_files( struct locked_file **prelnkptradr, /* Search our list of locked files */ struct locked_file **curlnkptradr ); /* (pointer to a pointer to a structure of type locked_file) */ int edx_parse( int output_filename_buffer_length, /* Parse a filename */ char *output_filename_buffer, int *output_filename_length, int input_filename_buffer_length, char *input_filename_buffer, int pdn ); /* MISCELLANEOUS */ void edx_setdef(); /* Change users default directory */ void set_logical(); /* Create a logical name */ void set_symbol(); /* Create a DCL symbol */ void show_logical(); /* Show translation of a logical name */ void show_symbol(); /* Show translation of a DCL symbol */ void delete_file(); /* Delete a file */ void tra_ebc_asc(); /* Translate EBCDIC to ASCII */ void tra_asc_ebc(); /* Translate ASCII to EBCDIC */ /* LIBRARIAN */ void librarian(); void lbr_init(int access); void lbr_close(); void lbr_readnext(); void lbr_writenext(); void lbr_replace(); /*============================================================================= TITLE TPU$CALLUSER Functional Description: The sole purpose of this jacket routine is to ensure we always return a success status to TPU, otherwise TPU will take the ON_ERROR - ENDON_ERROR action if one exists or it will print a message "Call user routine failed with status nnnnnnnn", */ unsigned int TPU$CALLUSER( int *incode, struct dsc$descriptor_d *instr_z_ptr, struct dsc$descriptor_d *outstr_d_ptr ) { tpu_calluser( incode, instr_z_ptr, outstr_d_ptr ); return(SS$_NORMAL); } /*---------------------------------------------------------------------------*/ void tpu_calluser( int *incode, struct dsc$descriptor_d *instr_z_ptr, struct dsc$descriptor_d *outstr_d_ptr ) { int incodeh; VAXC$ESTABLISH(handler); /* Establish handler */ /* GET INCODE. COMPARE WITH SET_MESSAGE_FLAGS */ incodeh = (*incode & 0xFFFF0000); if (incodeh == INCODE_SENDING_MESSAGE_FLAGS) { msgflgs = (*incode & 0x0000FFFF); /* low word is message flags */ return; } /* COPY INPUT DESCRIPTOR TO GLOBAL VARIABLE also setting class */ instr_desc.dsc$w_length = instr_z_ptr->dsc$w_length; instr_desc.dsc$a_pointer = instr_z_ptr->dsc$a_pointer; /* SET GLOBAL VARIABLE PTR to incode */ incode_ptr = incode; /* SET GLOBAL VARIABLE PTR to output descriptor */ outstr_desc_ptr = outstr_d_ptr; switch (incodeh) { case INCODE_SYSTEM: switch(*incode) { case INCODE_LOCK_FILE: lock_file(); return; case INCODE_UNLOCK_FILE: unlock_file(); return; case INCODE_SHOW_LOGICAL: show_logical(); return; case INCODE_SHOW_SYMBOL: show_symbol(); return; /*case INCODE_SIGNAL_ERROR_MESSAGE: edx_sigmsg(); return; no longer used */ case INCODE_CHECK_IF_FILE_IS_LOCKED: edx_ckfilk(); return; case INCODE_SET_DEFAULT_DIRECTORY: edx_setdef(); return; case INCODE_DEFINE_LOGICAL_NAME: set_logical(); return; case INCODE_SHOW_IDENT_NUMBER: show_id(); return; case INCODE_DELETE_FILE: delete_file(); return; case INCODE_SET_SYMBOL: set_symbol(); return; default: edx_signal(1, byref(EDX__UNKNCODE) ); return; } case INCODE_DIRECTORY: edx_directory(); return; case INCODE_TRANSLATE: switch (*incode) { case INCODE_TRANS_EBCDIC_TO_ASCII: tra_ebc_asc(); return; case INCODE_TRANS_ASCII_TO_EBCDIC: tra_asc_ebc(); return; default: edx_signal(1, byref(EDX__UNKNCODE) ); return; } case INCODE_SORT: edx_sort(); return; case INCODE_SPELL: edx_spell(); return; case INCODE_LIBRARIAN: librarian(); return; #if DEBUG case INCODE_HANDLER_TEST: test_handler(); return; #endif default: edx_signal(1, byref(EDX__UNKNCODE) ); return; } } /*----------------------------------------------------------------------------- .SBTTL SHOW IDENT VERSION NUMBER Functional Description: This routine returns the ident version number of this module Calling Sequence: show_id(); ---------------------------------------------------------------------------*/ void show_id() { int sig_array[3] = { SUCCESS, LENGTH(IDENT), IDENT }; fmtoutstr( 3, &sig_array ); } /*--------------------------------------------------------------------------- .SBTTL FMTOUTSTR Functional Description: This routine combines the return code in RETCODE with the return string specified in sig_array. Calling Sequence: fmtoutstr(nargs,sig_array); Argument inputs: nargs - Number of array elements in sig_array (1 or 3 by value) sig_array - sig_array[0] = return code (by value) sig_array[1] = [length of output string] (optional, by value) sig_array[2] = [address of output string] (optional, by value) Global inputs: outstr_desc_ptr = address of outstr_desc Outputs: outstr_desc = output string (by descriptor). output string is of the form: "nnnnnnnnnSTRING..." nnnnnnnnn = 9 character decimal return status for EDX STRING... = string returned for EDX ----------------------------------------------------------------------------*/ void fmtoutstr( int nargs, int *sig_array ) { int status; int arg_count = 0; char fao_output[10]; /* 9 for length 9 field, 10th as unused trailing ASCIZ NULL */ struct dsc$descriptor_s retstr_desc = { 0, DSC$K_DTYPE_T, DSC$K_CLASS_S, 0 }; $DESCRIPTOR(fao_output_desc, fao_output); static const $DESCRIPTOR(ctloutstr1, "!9ZL"); sys$fao( &ctloutstr1, /* call sys$fao with 1 argument */ &fao_output_desc.dsc$w_length, &fao_output_desc, sig_array[0] ); str$copy_dx( outstr_desc_ptr, &fao_output_desc ); /* copy to outstr */ if (nargs == 3) { retstr_desc.dsc$w_length = sig_array[1]; retstr_desc.dsc$a_pointer = sig_array[2]; str$append( outstr_desc_ptr, &retstr_desc ); /* append retstr to end of retcode */ } } /*--------------------------------------------------------------------------- .SBTTL EDX_SIGNAL Functional Description: This routine prints message text to the message buffer. The routine input is modeled after LIB$SIGNAL. (See description of LIB$SIGNAL) Calling Sequence: edx_signal(nargs,sig_array); Argument inputs: nargs - Number of array elements in sig_array (by value) sig_array - sig_array[0] = condition-value1 . = [number-of-args-for-condition-value1] . = [FAO-arg1] . = [,...] . = [FAO-argn] . = [condition-value2] . = [number-of-args-for-condition-value2] . = [FAO-arg2] . = [,...] . = [FAO-argn] . = [etc.] condition-value1 The condition to be signaled. (by value) FAO-arg1 (unspecified. Values sent directly to FAO.) etc. Outline: 1. Check severity of message to signal. If FATAL then use full message format (facility, identification, severity, text). Otherwise obtain current TPU message flags by calling ourselves using TPU$EXECUTE_COMMAND. The TPU command executed is: EDTN$X_DUMMY := CALL_USER( ^x00020000 + GET_INFO(SYSTEM,"message_flags"), "") ) which parses to: EDTN$X_DUMMY := CALL_USER( ^x0002000F, "") where 'F' is the current setting of the message flags (0-F hex). This value gets stored in msgflgs. 2. Check TPU VERSION & UPDATE. If 2.4 or above then UPDATE(MESSAGE_WINDOW). This fixes bug in VMS 5.3 so next message will appear properly LOOP 3. Call sys$getmsg to get message text 4. Call sys$fao to process message text 5. Call tpu$message to write message text to tpu message_buffer. ENDLOOP (until sig_array exhausted) ;-----------------------------------------------------------------------------*/ void edx_signal( int nargs, int *sig_array ) { int status; int arg_count = 0; char fao_output[BUFLEN]; char message_string[BUFLEN]; $DESCRIPTOR(fao_output_desc, fao_output); $DESCRIPTOR(message_string_desc, message_string); $CONST_DESCRIP(msgflgcmd_desc, "EDTN$X_DUMMY:=CALL_USER(131072+GET_INFO(SYSTEM,'MESSAGE_FLAGS'),'')" ); $CONST_DESCRIP(updmsgcmd_desc, "IF GET_INFO(SYSTEM,'DISPLAY') THEN UPDATE(MESSAGE_WINDOW) ENDIF;" ); /* get current value of message flags */ tpu$execute_command(&msgflgcmd_desc); /* Now do special check for VMS 5.3 bug. If running VAXTPU 2.4 or above, then make call to update(message_window) BEFORE sending message to message buffer. Someday when they fix this bug we'll branch if VAXTPU above where bug is fixed. */ if (TPU$GL_VERSION == 2 && TPU$GL_UPDATE >= 4) tpu$execute_command(&updmsgcmd_desc); /* Check severity of primary message. Ring bell if not good. */ if ( !(sig_array[0] & STS$M_SUCCESS) ) /* if WARNING, ERROR, or FATAL (not SUCCESS or INFORMATIONAL) */ { if ( !((sig_array[0] & STS$M_SEVERITY) == STS$K_WARNING)) /* no bell for warning */ lib$put_output(&bell_desc); /* Ring terminal bell for ERROR or FATAL */ /* If message is fatal, use full message flags */ if ( (sig_array[0] & STS$M_SEVERITY) == STS$K_SEVERE) msgflgs = 15; /* Use full message format */ } /* BEGIN MAIN LOOP */ do { message_string_desc.dsc$w_length = BUFLEN; /* initialize buffer length in message string */ fao_output_desc.dsc$w_length = BUFLEN; /* initialize buffer length in for FAO output string */ /* Get message text */ sys$getmsg(sig_array[arg_count], /* msgid */ &message_string_desc.dsc$w_length, /* msglen (by reference) */ &message_string_desc, /* bufadr (by descriptor) */ msgflgs, /* flags (by value) */ 0 ); /* outadr */ /* Check for FAO arguments */ ++arg_count; /* sig_array[arg_count] = [number-of-args-for-condition-value1] */ if (arg_count < nargs) /* Number of arguments used < total number of arguments */ { sys$faol( &message_string_desc, /* ctrstr */ &fao_output_desc.dsc$w_length, /* outlen */ &fao_output_desc, /* outbuf */ &sig_array[arg_count+1] ); /* prmlst *//* sig_array[arg_count] = FAO argument #1 */ tpu$message( &fao_output_desc ); } else { /* Print out last (or only) message */ tpu$message( &message_string_desc ); return; } /* See if there's another message to do */ arg_count += sig_array[arg_count] + 1; } while(arg_count < nargs); return; } /*--------------------------------------------------------------------------- .SBTTL CONDITION HANDLER Functional Description: This routine handles unexpected errors that are signaled. The error is printed to the TPU message_buffer. If the error is severe, an attempt is made to return to TPU, otherwise the error is non-fatal and an attempt is made to continue. A signal of any type including Informational (-I-) is not resignaled because the TPU CALL_USER routine establishes it's own condition handler that will be called next if our handler does not handle the condition, and the TPU handler calls LIB$SIG_TO_RET if the signaled condition does not have a facility value of TPU. Therefore we never return with SS$_RESIGNAL (unless unwinding). Calling Sequence: This routine is called by the operating system whenever a condition is signaled. A memory access violation or a divide by zero error for example would be signaled. Argument inputs: sigargs array (both VAX/VMS & OpenVMS Alpha AXP): ----------------------------------------- | vector length | sig_array[0] ----------------------------------------- | condition value | sig_array[1] ----------------------------------------- |Additional arguments as required (or none) | . | | . | ----------------------------------------- | PC | ----------------------------------------- | PSL | ----------------------------------------- Notes: Even though many conditions do not use PC & PSL in their FAO arguments, we always send them to EDX_SIGNAL because it's easier that way. When EDX_SIGNAL calls FAO, FAO will use only what it needs. */ unsigned long int handler(unsigned int *sigargs, unsigned int *mechargs) { int i; int sig_array[16]; /* Let's hope no condition uses more than 12 FAO arguments */ if (sigargs[1] == SS$_UNWIND) return(0); if (sigargs[1] & STS$K_SEVERE) /* check for fatal status */ { sig_array[0] = EDX__UNEXPERR; /* Unexpected Fatal Error */ sig_array[1] = 0; /* 0 FAO arguments for EDX__UNEXPERR */ sig_array[2] = sigargs[1]; /* condition value */ sig_array[3] = sigargs[0]-1; /* number of FAO arguments for condition value */ for( i = 4; i < sigargs[0]+3 && i < 16; ++i ) sig_array[i] = sigargs[i-2]; /* the FAO arguments */ edx_signal( i, sig_array); /* print the error messages */ i = sys$unwind(0,0); /* unwind stack */ } else /* handle unexpected fatal errors */ { sig_array[0] = sigargs[1]; /* condition value */ sig_array[1] = sigargs[0]-1; /* number of FAO arguments for condition value */ for( i = 2; i < sigargs[0]+1 && i < 16; ++i ) sig_array[i] = sigargs[i]; /* the FAO arguments */ edx_signal( i, sig_array); /* print the error messages */ return(SS$_CONTINUE); /* return from exception */ } } /*-------------------------------------------------------------------------*/ #if DEBUG /* SOME TESTS OF THE ERROR HANDLER */ void test_handler() { static readonly int a = 1; int incodel; int d = 0; int e,u,v; incodel = (*incode_ptr & 0x0000FFFF); switch (incodel) /* Case entry point to jump to */ { case 1: test_handler_sub(&a); break; /* Generate access violation */ case 2: e = 1/d; break; /* Divide by zero */ case 3: v = TPU$GL_VERSION; fmtoutstr( 1, &v ); break; case 4: u = TPU$GL_UPDATE; fmtoutstr( 1, &u ); break; default: edx_signal(1, byref(EDX__UNKNCODE) ); break; } } void test_handler_sub(int *m) { *m = 0; /* Test handler by generating access violation. Modify readonly variable */ } #endif /* =========================================================================== DIRECTORY =========================================================================== .SUBTITLE EDX_DIRECTORY Functional Description: Displays a directory listing Calling Sequence: edx_directory(); Argument inputs: incode_ptr = Points to incode. Low word of incode is code describing where to reenter (which case to go to). instr_desc = Directory command (by descriptor). The directory command is of the form: DIRECTORY [/SIZE] [/DATE] [dir-spec] Outputs: OUTSTR = Line to place in DIR_BUFFER (by descriptor) RETCODE = Code to use for consecutive calls (note: OUTSTR and RETCODE are placed together in the outstr_desc string returned by FMTOUTSTR routine. RETCODE is stored in first 9 characters of the returned output string.) Comments: For regular directory listing, filenames are placed 4 across staring at column offsets 0,20,40,60. For /SIZE or /DATE qualifiers, filenames are placed at column offset 0. Then if col is greater than 18 line is written and new line started Error starts at column offset 19 if there is one else /SIZE starts at offset 19 if specified and /DATE starts at offset 29 if specified COL is used as both a length indicator and a column offset indicator. The value of COL is the number of characters in OUTLINE. When the value of COL is added to the base address of OUTLINE, the result is the address to start adding text to when appending text out OUTLINE. Outline: 1. Entry code is cased for consecutive reentries dstrt: 2. On initial entry A. The default setting for /SIZE and /DATE in Dirflgs is initialized by checking for a defined symbol DIR. For example, the user may have defined DIR :== DIRECTORY/SIZE/DATE in which case we'll give him the /SIZE /DATE by default unless he specifically negates it with /NOSIZE /NODATE. B. The EDX directory command is parsed for /SIZE, /DATE, and the dir-spec. C. a. XABDAT block initialized b. XABFHC block initialized c. NAM block initialized d. FAB block initialized. Dir-spec placed into FAB. e. Variables are initialized D. Set DIRFLGS according to /SIZE and /DATE qualifiers parse: 3. $PARSE FAB to prepare for wildcard operations srchlp: 4. $SEARCH FAB for next filename A. If 'No more files' or 'File Not Found' or other error, exit with code. B. IF this is the first call, return first with the expanded string for the window status line. ELSE IF the returned directory root has changed then print the contents of outline and then proot: print the new directory heading. nxttab: 5. Add new file to outline. A. Move COL offset pointer into outline to next tab stop (0,20,40,60) Print outline if line full. adfile: B. Add filename to outline 7. Check DIRFLGS for qualifiers like /SIZE and /DATE A. If no qualifiers present then goto srchlp: B. Print outline if filename too long (overflows into SIZE field) getatr: C. Get file attributes D. Add size info if requested E. Add date info if requested F. Print outine G. Goto srchlp: 8. Repeat until exit by step 4. Description: The TPU editor calls us with an initial code of START. We return to the TPU editor with an output string and a code telling the editor what to do with the output string. Usually the TPU editor is to print the output string to the dir_buffer, occasionally it must add a blank line or two. It then calls us back passing to us the value of code we gave it. The value of code tells us where to jump back to. The TPU editor continues to call us until we pass it the NMF_ERR code. Coding notes: The logic would flow smoothly if everytime we wanted to print a line we just called a subroutine to do so. However, in order to print a line here we must return to the caller. Thus we need to use goto statements and labels so we can jump back in to where we left off so the logic still flows smoothly. It's actually possible to get a file's size and date from the file without actually opening the file by using the RMS $DISPLAY call instead. We use $OPEN here to better emulate the $ DIR/SIZE/DATE command which will give us an error if the file is currently open or if we don't have enough privilege to open the file. ---------------------------------------------------------------------------*/ #define DIR_DSTRT 1 /* first call */ #define DIR_SRCHLP 2 /* TPU prints outline and calls again */ #define DIR_PROOT 3 /* TPU prints outline followed by one blank lines and calls us again */ #define DIR_NXTTAB 4 /* TPU prints outline followed by one blank line and calls us again */ #define DIR_ADFILE 5 /* TPU prints outline and calls us again */ #define DIR_GETATR 6 /* TPU prints outline and calls us again */ #define DIR_RMS_ERR 7 /* Return code. We print error message. TPU exits. */ #define DIR_FNF_ERR 8 /* Return code. TPU prints 'no files found' and exits */ #define DIR_NMF_ERR 9 /* Return code. TPU prints outline and exits */ #define DIR_PSUBTOT 10 /* TPU prints outline followed by one blank line and calls us again */ #define DIR_SUMMARY 11 /* TPU prints outline followed by one blank line and calls us again */ #define DIR_GTTL 12 /* TPU prints outline containing grand totals and quits. All done. */ /* dir flags */ #define DIRFLGS_GET_SIZE 1 #define DIRFLGS_GET_DATE 2 /* ;column offset positions & constants ;....v....1....v....2....v....3....v....4....v....5....v....6....v....7....v.... ;FILENAMEABC.EFG;21 00000000 25-JUL-1988 17:14 */ #define DIR_DATCOL 29 /* offset to start date */ #define DIR_SIZCOL 19 /* offset to start size */ #define DIR_SIZLEN 8 /* length of size field */ #define DIR_DATLEN 17 /* length of date field */ /* MACROS */ #define PRINT_OUTLINE(reentry_code) {\ sig_array[0] = reentry_code;\ sig_array[1] = col;\ sig_array[2] = outline;\ fmtoutstr( 3, &sig_array );\ memset( outline, ' ', BUFLEN );\ col = 0;\ return;\ } /*---------------------------------------------------------------------------*/ void edx_directory() { unsigned short retlen; int status; int incodel; int nxtcol; int file_size; int sig_array[3]; /* used by PRINT_OUTLINE macro */ static int total_directories;/* Total number of directories */ static int total_files; /* Total number of files for this directory */ static int total_blocks; /* Total number of blocks for this directory (when using /SIZE) */ static int grand_total_files; /* Grand total number of files for all directories */ static int grand_total_blocks; /* Grand total number of blocks for all directories */ static int col; /* column offset. (MBR) = Must Be Retained between calls */ static int dirflgs; /* indicates if /SIZE or /DATE specified. (MBR) */ static int rootlen; /* length of node::device:[directory] (MBR) */ static char input_file[NAM$C_MAXRSS]; static char result_file[NAM$C_MAXRSS]; static char expanded_file[NAM$C_MAXRSS]; static char root[NAM$C_MAXRSS]; static char outline[BUFLEN]; /* used by PRINT_OUTLINE macro */ static struct FAB dirfab; static struct NAM dirnam; static struct XABFHC dirxabfhc; static struct XABDAT dirxabdat; struct dsc$descriptor_s outline_desc = { BUFLEN, DSC$K_DTYPE_T, DSC$K_CLASS_S, outline }; struct dsc$descriptor_s outlnsize_desc = { DIR_SIZLEN, DSC$K_DTYPE_T, DSC$K_CLASS_S, &outline[DIR_SIZCOL] }; struct dsc$descriptor_s outlndate_desc = { DIR_DATLEN, DSC$K_DTYPE_T, DSC$K_CLASS_S, &outline[DIR_DATCOL] }; struct dsc$descriptor_s errmsg_desc; $DESCRIPTOR(input_file_desc,input_file); $CONST_DESCRIP(fao_total, "Total of !SL file!%S."); $CONST_DESCRIP(fao_totsize, "Total of !SL file!%S, !SL block!%S."); $CONST_DESCRIP(fao_gtotal, "Grand total of !UL directories, !UL file!%S."); $CONST_DESCRIP(fao_gtotsize, "Grand total of !UL directories, !UL file!%S, !UL block!%S."); $CONST_DESCRIP(dirspec_desc,"DIRSPEC"); /* Entry code is cased for reentry point */ incodel = (*incode_ptr & 0x0000FFFF); switch (incodel) { case DIR_DSTRT: goto dstrt; /* Initial entry */ case DIR_SRCHLP: goto srchlp; /* TPU printed outline and called us again */ case DIR_PSUBTOT: goto psubtot; /* TPU printed outline followed by one blank line and called us again */ case DIR_PROOT: goto proot; /* TPU printed outline followed by one blank line and called us again */ case DIR_NXTTAB: goto nxttab; /* TPU printed outline followed by one blank line and called us again */ case DIR_ADFILE: goto adfile; /* TPU printed outline and called us again */ case DIR_GETATR: goto getatr; /* TPU printed outline and called us again */ case DIR_SUMMARY: goto summary; /* TPU printed outline containing last filenames, followed by one blank line, and called us again to get totals and grand totals */ case DIR_GTTL: goto gttl; /* TPU printed outline containing totals for this directory, followed by one blank line, and called us again to get grand totals */ default: edx_signal(1, byref(EDX__UNKNCODE) ); return; } dstrt: getdefdirflgs(&dirflgs); /* Dirflgs is initialized */ status = cli$dcl_parse(&instr_desc,&edx_commands); /* The directory command is parsed using CLI$DCL_PARSE for /SIZE, /DATE, and dir-spec. */ if ( !(status & STS$M_SUCCESS) ) return; /* return on error. CLI$DCL_PARSE signaled error and our condition handler printed the error. */ /* INITIALIZE XABDAT BLOCK */ dirxabdat = cc$rms_xabdat; /* initialize XABDAT */ /* INITIALIZE XABFHC BLOCK */ dirxabfhc = cc$rms_xabfhc; /* initialize XABFHC */ dirxabfhc.xab$l_nxt = &dirxabdat; /* Address of next XAB (XABDAT) */ /* INITIALIZE NAM BLOCK */ dirnam = cc$rms_nam; /* initialize NAM */ dirnam.nam$b_rss = NAM$C_MAXRSS; /* Resultant file name string size */ dirnam.nam$l_rsa = (char *) result_file; /* Resultant file name string address */ dirnam.nam$b_ess = NAM$C_MAXRSS; /* Expanded file name string size */ dirnam.nam$l_esa = (char *) expanded_file; /* Expanded file name string address */ /* INITIALIZE FAB BLOCK */ dirfab = cc$rms_fab; /* initialize FAB */ dirfab.fab$l_nam = &dirnam; /* NAM block address */ dirfab.fab$l_xab = &dirxabfhc; /* XAB block address (XABFHC) */ dirfab.fab$l_fop = FAB$M_NAM; /* FAB Options = use NAM block */ dirfab.fab$l_dna = (char *) "*.*;*"; /* Default file name of "*.*;*" */ dirfab.fab$b_dns = 5; /* Default file name length */ dirfab.fab$l_fna = (char *) input_file; /* Address of input string containing dir-spec */ dirfab.fab$b_fac = FAB$M_GET; /* File Access options = GET */ /* Allow read/write sharing in case we have to open the file to get the file attributes */ dirfab.fab$b_shr = FAB$M_SHRGET | FAB$M_SHRUPD | FAB$M_SHRPUT | FAB$M_SHRDEL; /* INITIALIZE VARIABLES */ memset( outline, ' ', BUFLEN ); /* clear outline */ total_directories = 0; /* start with 0 directories */ col = 0; /* Set column offset := 0 */ grand_total_files = 0; /* Grand total number of files for all directories */ grand_total_blocks = 0; /* Grand total number of blocks for all directories */ /* GET DIR-SPEC INTO FAB BLOCK BY CALLING CLI$GET_VALUE */ cli$get_value( &dirspec_desc, &input_file_desc, &retlen); /* Dir-spec placed into FAB by calling cli$get_value */ dirfab.fab$b_fns = retlen; status = cli$present( s_descrip("SIZE") ); /* check for /SIZE present in command line */ if ( status == CLI$_PRESENT ) dirflgs |= DIRFLGS_GET_SIZE; else if ( status == CLI$_NEGATED ) dirflgs &= ~DIRFLGS_GET_SIZE; status = cli$present( s_descrip("DATE") ); /* check for /DATE present in command line */ if ( status == CLI$_PRESENT ) dirflgs |= DIRFLGS_GET_DATE; else if ( status == CLI$_NEGATED ) dirflgs &= ~DIRFLGS_GET_DATE; /* PARSE THE DIR-SPEC */ parse: status = sys$parse( &dirfab ); /* $PARSE FAB to prepare for wildcard operations */ if ( !(status & STS$M_SUCCESS) ) { edx_signal(1,&status); /* signal error (could be Directory Not Found, Invalid Device Name, etc) */ PRINT_OUTLINE(DIR_RMS_ERR) /* and return error */ } /* SEARCH LOOP */ srchlp: status = sys$search( &dirfab ); /* $SEARCH FAB for next filename */ switch (status) { case RMS$_NORMAL: break; /* continue if normal */ case RMS$_FNF: /* File Not Found */ PRINT_OUTLINE(DIR_FNF_ERR) /* return error */ case RMS$_NMF: /* 'No More Files' */ PRINT_OUTLINE(DIR_SUMMARY) /* Print last line of files followed by summary of total and grand_total and then quit */ default: edx_signal(1,&status); /* Wasn't any of the expected errors so signal error and return */ PRINT_OUTLINE(DIR_NMF_ERR) /* return error */ } /* Print new directory heading if needed */ /* WE HAVE A NEW FILENAME TO PROCESS. */ /* PRINT OUT NEW DIRECTORY HEADING IF NEEDED */ if (total_directories == 0) { /* Print directory specification */ total_directories = 1; col = dirnam.nam$b_esl; PRINT_OUTLINE(DIR_PROOT) /* If this is the first call, return first with the expanded string for the window status line. Then go print the directory heading. */ } if ( strncmp( root, result_file, rootlen ) == 0 ) goto nxttab; /* Branch if root is still same */ ++total_directories; /* We have a new directory heading */ PRINT_OUTLINE(DIR_PSUBTOT) /* print last line of filenames followed by totals for this directory followed by new directory heading */ psubtot: if (total_directories > 1) /* IF MULTIPLE_ROOTS THEN FIRST PRINT TOTAL FOR PREVIOUS ROOT */ if ( (dirflgs & DIRFLGS_GET_SIZE) == 0 ) sys$fao( &fao_total, /* "Total of !SL files." */ &col, &outline_desc, total_files ); else sys$fao( &fao_totsize, /* "Total of !SL files, !SL blocks." */ &col, &outline_desc, total_files, total_blocks ); PRINT_OUTLINE(DIR_PROOT) /* print totals for this directory followed by new directory heading */ proot: /* PRINT NEW DIRECTORY HEADING */ /* FILENAME IS OF FORM NODE::DEV:[DIR]NAME.TYPE;VER */ total_files = 0; /* reset number of files to 0 */ total_blocks = 0; /* reset total size to 0 */ rootlen = dirnam.nam$b_node + dirnam.nam$b_dev + dirnam.nam$b_dir; /* Calculate length of node::dev[dir] */ memcpy(root,result_file,rootlen); /* root = result_name(1:rootlen); */ memcpy(outline,"Directory ",10); /* outline = "Directory " (10 chars) */ memcpy(&outline[10],result_file,rootlen); /* outline = "Directory {dirspec}" */ col = 10 + rootlen; /* Length of outline = "Directory {dirspec}" */ PRINT_OUTLINE(DIR_NXTTAB) /* print outline and go to nxttab: */ nxttab: /* Add new file to outline. */ /* Move COL offset pointer into outline to next tab stop (0,20,40,60) */ /* Print outline if line full. */ /* MOVE TO NEXT TAB STOP (0,20,40,60) */ if (col != 0) /* If COL = 0 then go add next filename */ { if (col < 20) nxtcol = 20; /* Advance COL to 20 */ else if (col < 40) nxtcol = 40; /* Advance COL to 40 */ else if (col < 60) nxtcol = 60; /* Advance COL to 60 */ else nxtcol = 80; /* (insures we will print this line below */ if (nxtcol + dirnam.nam$b_name + dirnam.nam$b_type + dirnam.nam$b_ver >= 80) { /* print line. Another filename won't fit on this line */ PRINT_OUTLINE(DIR_ADFILE) /* print outline and go to adfile: */ } col = nxtcol; /* Advance to next column and add next filename */ }/* if (col != 0) */ adfile: /* Add filename to outline */ /* ADD NEW FILENAME TO OUTLINE */ memcpy(&outline[col],dirnam.nam$l_name, dirnam.nam$b_name + dirnam.nam$b_type + dirnam.nam$b_ver); ++total_files; /* Increment number of files in this directory listing */ ++grand_total_files; col = col + dirnam.nam$b_name + dirnam.nam$b_type + dirnam.nam$b_ver; /* Check DIRFLGS for qualifiers like /SIZE and /DATE */ /* PRINT OUTLINE IF FILENAME TOO LONG */ if (dirflgs == 0) goto srchlp; /* no qualifiers present, goto loop */ if (col >= DIR_SIZCOL) /* Check column offset. Check for extra-long filename */ PRINT_OUTLINE(DIR_GETATR) /* print outline and go to adfile: */ getatr: /* /SIZE AND/OR /DATE PRESENT */ status = sys$open( &dirfab ); /* Get file attributes */ if ( !(status & STS$M_SUCCESS) ) { short int errmsg_len; errmsg_desc.dsc$w_length = NAM$C_MAXRSS - DIR_SIZCOL; errmsg_desc.dsc$b_class = DSC$K_CLASS_S; errmsg_desc.dsc$b_dtype = DSC$K_DTYPE_T; errmsg_desc.dsc$a_pointer = &outline + DIR_SIZCOL; sys$getmsg( status, &errmsg_len, &errmsg_desc, 1, 0 ); col = DIR_SIZCOL + errmsg_len; PRINT_OUTLINE(DIR_SRCHLP) /* print outline containing error message and go to srchlp: */ } /* File successfully opened. Close file and process qualifiers. */ sys$close( &dirfab ); if ( (dirflgs & DIRFLGS_GET_SIZE) != 0 ) { file_size = dirxabfhc.xab$l_ebk; if ( dirxabfhc.xab$w_ffb == 0 ) --file_size; /* If first free byte = 0 then don't count last block */ total_blocks += file_size; /* add to total */ grand_total_blocks += file_size; /* add to total */ sys$fao( s_descrip("!8UL"), /* ctrstr (DIR_SIZLEN=8) */ 0, /* outlen */ &outlnsize_desc, /* outbuf (outline starting at offset for size) */ file_size ); /* P1 */ col = DIR_SIZCOL + DIR_SIZLEN; } if ( (dirflgs & DIRFLGS_GET_DATE) != 0 ) { sys$asctim( 0, /* timlen */ &outlndate_desc, /* timbuf */ &dirxabdat.xab$q_cdt, /* timadr (file creation date) */ 0 ); /* cvtflg */ col = DIR_DATCOL + DIR_DATLEN; /* Set col pointer to end of date */ } PRINT_OUTLINE(DIR_SRCHLP) /* print outline and go to srchlp: */ summary: if ( (dirflgs & DIRFLGS_GET_SIZE) == 0 ) sys$fao( &fao_total, /* "Total of !SL files, !SL blocks." */ &col, &outline_desc, total_files ); else sys$fao( &fao_totsize, /* "Total of !SL files." */ &col, &outline_desc, total_files, total_blocks ); if (total_directories > 1) /* IF MULTIPLE_ROOTS THEN FIRST PRINT TOTAL FOR PREVIOUS ROOT */ PRINT_OUTLINE(DIR_GTTL) /* print totals for this directory followed by one blank line. Call again for grand totals. */ else PRINT_OUTLINE(DIR_NMF_ERR); /* print totals and quit. We're done. */ gttl: if ( (dirflgs & DIRFLGS_GET_SIZE) == 0 ) sys$fao( &fao_gtotal, /* "Grand total of !UL directories, !UL files." */ &col, &outline_desc, total_directories, grand_total_files ); else sys$fao( &fao_gtotsize, /* "Grand total of !UL directories, !UL files, !UL blocks." */ &col, &outline_desc, total_directories, grand_total_files, grand_total_blocks ); PRINT_OUTLINE(DIR_NMF_ERR); /* print grand totals and quit. We're done. */ } /*--------------------------------------------------------------------------- .SUBTITLE GETDEFDIRFLGS Functional Description: Attempts to translate DCL symbol 'DIR' and determine if /SIZE or /DATE qualifiers are present. Calling Sequence: getdefdirflgs(dirflgs); Arguments: dirflgs = int to put dirflgs in, (by reference). Outputs: dirflgs = DIRFLGS_GET_SIZE bit set if /SIZE is present DIRFLGS_GET_DATE bit set if /DATE is present Outline: 1. LIB$GET_SYMBOL is called to obtain the symbol translation 2. CLI$PRESENT is called to determine if qualifiers are present and dirflgs is set accordingly. ----------------------------------------------------------------------------*/ unsigned long int null_handler(unsigned int *sigargs, unsigned int *mechargs) { return(SS$_CONTINUE); /* dismiss all exceptions */ } void getdefdirflgs(int *dirflgs_ptr) { int status; int tblind; char buffer[BUFLEN]; $DESCRIPTOR(buffer_desc,buffer); VAXC$ESTABLISH(null_handler); /* Suppress all signals from CLI$DCL_PARSE and CLI$PRESENT */ *dirflgs_ptr = 0; /* Initialize dirflgs */ if ( lib$get_symbol( s_descrip("DIR"), &buffer_desc, &buffer_desc.dsc$w_length, &tblind ) /* Translate symbol */ & STS$M_SUCCESS ) /* if translation succeeded */ { status = cli$dcl_parse( &buffer_desc, &edx_commands ); /* Parse input string */ if (cli$present(s_descrip("SIZE")) == CLI$_PRESENT) /* if /SIZE was present */ *dirflgs_ptr |= DIRFLGS_GET_SIZE; /* then set bit for /DATE */ if (cli$present(s_descrip("DATE")) == CLI$_PRESENT) /* if /DATE was present */ *dirflgs_ptr |= DIRFLGS_GET_DATE; /* then set bit for /DATE */ } } /* ============================================================================== EDX SPELL ============================================================================== EDX DICTIONARY LEXICAL DATABASE: Source The EDX spelling dictionary now contains over 90,000 words, all of which are believed to be correctly spelled. Every effort has gone into verifying the integrity of the EDX spelling dictionary. All of the words in the lexical database file DICTIONARY.LEX have been cross checked against three separate spelling checkers commercially available for home personal computers. These are: The Random House Proofreader version 2.00 Wang Electronic Publishing, Inc. P.O. Box 367 Tijeras, NM 87059 WordPerfect Speller from WordPerfect version 5.1 WordPerfect Corporation 1555 N. Technology Way Orem, Utah 84057 Spell Check module of Multi-Edit Professional version 6.0 American Cybernetics 1830 W. University Dr. #112 Tempe, AZ 85281 In the course of cross checking the dictionaries it was found that WordPerfect 5.1 contained 90 words which were either misspelled (such as "postoffice") or so obscure that they were not found in any of the unabridged dictionaries our local library had nor were they found in any other reference book we could think of to try. WordPerfect Corporation has been notified of these words. It was also found that Multi-Edit Professional version 6.0 contained 365 words which were definitely misspellings. American Cybernetics has been notified of these words. In addition it was found that the popular Vassar spelling checker available on DECUS tapes contains about 20,000 misspelled words. The words in the common word list file COMMONWORDS.LEX came from "The American Heritage Word Frequency Book, copyright 1971 by American Heritage Publishing Co, Inc.". EDX DICTIONARY: Layout It is possible to examine the EDX dictionary file EDX_DICTIONARY.DAT with the EDX editor. Invoke the EDX editor and read in the file as you would any other file. Each line of the file represents one 512 byte block of the file. The line number corresponds to the Virtual Block Number (VBN) of the block. DICTIONARY FILE LAYOUT: To optimize performance we use direct disk block transfers which bypasses the concept of file records. The disk block size is 512 bytes. Once the file is opened for block I/O (by specifying BIO in the FAC field of the FAB), the disk blocks can be randomly accessed by specifying the Virtual Block Number (VBN) of the block you wish to read or write. Each section of the database begins on a block boundary. DICTIONARY FILE LAYOUT: -------------------- | HEADER BLOCK | (VBN 1) | | -------------------- | LEXICAL DATABASE | (VBN 2) | | | | | | | | | | -------------------- | INDEX | (around VBN 2000) | | | | -------------------- | COMMON WORD LIST | (around VBN 2008) | | -------------------- HEADER Virtual Block number 1 (VBN 1) contains information about the rest of the database, where it's located in the file and how long each section is. The figure below describes the header block in detail. FILE HEADER (VBN 1) -------------------------------------------------- | 't' | 'c' | 'i' | 'd' | 'X' | 'D' | 'E' |VERSNO| 00 header -------------------------------------------------- | LEXVBN | 08 Virtual Block Number of disk block where lexical database begins -------------------------------------------------- | LEXBLN | 10 Length in blocks of lexical database section -------------------------------------------------- | INDVBN | 18 Virtual Block Number of disk block where index database begins -------------------------------------------------- | INDLEN | 20 Length in bytes of index database -------------------------------------------------- | INDSWD | 28 Size of each index word -------------------------------------------------- | DICPLN | 30 Dictionary Page length. Number of bytes (of lexical database) between index guide words -------------------------------------------------- | CWDVBN | 38 Virtual Block Number of disk block where common word list begins -------------------------------------------------- | CWDLEN | 40 Length in bytes of common word list -------------------------------------------------- | CWDMLN | 48 Length in bytes of longest word in common word list -------------------------------------------------- LEXICAL DATABASE: Format The Lexical Database portion contains the actual list of ~90,000 words, hopefully some of which are correctly spelled. The words must appear in alphabetical order, with a single length-byte preceding each word, the very last word in the lexical database stream is followed by a NULL byte. All alphabetic characters must be lowercase. The contiuous stream of characters looks like this: ".A.AARDVARK.AARDVARKS.AARON.AAVC.ABACK.ABACTERIAL.ABACUS.... 1 8 9 5 4 5 10 6 Here the (.) indicates a byte whos ASCII value is given below it. A section of this word list will later be searched for a match to an unknown word preceded by a length-byte of the words length, such as: search list for: ".AARON" 5 INDEXING CONCEPTS Page Length: Conceptually, our lexical database section is broken into fixed length pages. The value DICPLN stored in the header block indicates the page length (in bytes) used in the dictionary. Guide Words In a regular paper dictionary there is at the top of each page a guide word which indicates the first word for that page. In our dictionary we use fixed length guide words, saving only the first N letters of the first full word of each lexical database page. (Note that characters of a word may lie across a page boundary, we only accept the first full word after passing over those trailing characters of the previous word which started on the previous page.) The value of N is the value of INDSWD stored in the header block. If the guide word is less than N characters long it is blank padded to N characters. INDEX The index section is a contiguous stream of the ordered fixed length guide words. This is the information which helps us quickly zero in on where in the lexical database to search for a given word. An example may explain best: Assuming an index word size (INDSWD) of 4, and a page length DICPLN of 1024 bytes the first 4 pages of our dictionary would look like the following: A (guide word for page 1 is "A ") .A.AARDVARK.AARDVARKS.AARON.AAVC.ABACK.ABACTERIAL... (page 1 consists of first 2 blocks (1024 bytes) of the lexical database. ABJE (guide word for page 2 is first 4 letters of word ABJECT) OTICALLY.ABJECT.ABJECTION.ABJECTLY.ABJECTNESS... (page 2 consists of next 2 blocks (1024 bytes) of the lexical database) ABRE (guide word for page 3 is first 4 letters of word ABREAST) REACTION.ABREAST.ABRIDGE.ABRIDGED.ABRIDGEMENT... (page 3 consists of next 2 blocks (1024 bytes) of lexical database) ABST (guide word for page 4 consists of first 4 letters of word ABSTRACTION) STRACTING.ABSTRACTION.ABSTRACTIONAL.ABSTRACTIONISM... (page 4 consists of next 2 blocks (1024 bytes) of lexical database) The index contains the first 4 characters of the first full word of every page in the lexical database. The 4 character index words are packed together into a continuous stream of characters. For our above example, the index would look like: guide word: "A ABJEABREABST..." | | | | | page number: 1 2 3 4 ... The index of guide words may be randomly searched since we know each guide word is 4 characters long and we know where the index database starts and ends. Guide Word Index Searching algorythm The following is a brief description of the searching algorythm used by EDX in looking up a word in the dictionary. We start by performing a binary search on the index of guide words for a match to the first N characters of the target word we ultimately wish to find. There is however the posibility that 2 or more pages of our lexical database may have the same guide word since guide words are truncated to N characters. We therefore abandon the binary search if we happen to come across an exact match, and switch to a linear search of the guide words up and down until we positively identify the smallest possible group of pages in which our target word must lie. After identifying the target range of pages of the lexical database, we perform a linear search of those pages for our target word. The linear search is performed as follows: GIVEN: 'low' - dictionary page number to begin search on, below which word would not lie. 'high' - upper bound dictionary page number above which the word would not lie. 'lexdba' - Starting virtual address of lexical database. 'DICPLN' - length (in bytes) of a dictionary page. 1. Starting at lexdba+low*DICPLN, search forward character by character looking for a length-byte, which is identified by being less than ascii(32) 2. Check if length-byte = 0 indicateing End Of Lexical Database. If length-byte = 0 then Word Not Found. 3. Check if length-byte = length of target word If not then this can't be a match to the word we're looking for. 4. Else compare found word with target word starting with last character and moving to front of word. We do this because we already expect the first few characters of the word to match if we're looking in the right area of the dictionary. 5. If match then WORD FOUND -> EXITLOOP 6. Move forward length-byte + 1 bytes to next word. Check if length-byte address is beyond range we are searching. If so then WORD NOT FOUND -> EXITLOOP Else go to step 2 and loop. COMMON WORD LIST: The common word list is formatted the same as the lexical database section. It consists of a list of commonly used words with a length-byte preceeding each word. A NULL character follows the last word in the list. This is a short list (about 1 block in length) of very frequently used words, ordered by frequency of use, with the most frequently used word listed first. This list is searched first before a search through the index and main lexical database is made. LOADING THE EDX DICTIONARY: The EDX dictionary is not "read" into memory. Instead it is "mapped" into memory using the $CRMPSC service, which is considerably faster and doesn't use up any user page file quota (pgflquo). The dictionary could be loaded by first allocating about 2000 pages of memory, and then reading the entire file into the memory allocated. This would be quite slow due to the large size of the database. Also a user's pgflquo quota limits the total amount of memory a user may allocate, and the 2000 pages required for the database is a considerable amount of memory. $CRMPSC accomplishes the same result of allocating memory and then reading the file into memory, except it never allocates memory from the system, and it never reads in the file. Instead, it expands the process region by 2000 pages thereby instantly making new virtual memory available, and then it declares that the the physical file EDX_DICTIONARY.DAT itself is the paging file for that section of memory. The initialization is now complete, with hardly any work having been done. Now when the program attempts to read some of the dictionary that's in that memory range, a page fault will occur if that page is not already in memory and that page is automatically read into memory. And since we're not using the system paging file for this, the user's pgflquo quota is not affected. PERFORMANCE OPTIMIZATION There are 3 variables described above which affect the performance of this dictionary. You may play with these variables if you wish, and see what values work best. The varibles are: DICINDSWD ;INDSWD. Size of guide word in index DICDICPLN ;DICPLN. Dictionary Page Length in bytes PFC - Page Fault Cluster Size argument to $CRMPSC (INDSWD and DICPLN are changed in EDX_BLDDIC.C and then the file EDX_DICTIONARY.DAT is recreated using the new variables. PFC is the 12th and last argument in the call to $CRMPSC in file EDX_CALLUSER.C) INDSWD: (Index Size of Word) The smaller the guide word size, the smaller the index, and the less time it takes to search the index. However, a smaller guide word means the range of pages where a target word must lie may not be as narrow as it would if the guide words were longer. For example, if the guide word length were 2, and you were looking up the word 'ASSIMULATE', you would have to search all of the lexical database pages which had a guide word of 'AS'. Conversely, with a larger guide word size, the range of pages wherein a target guide word must lie can be narrowed down more. With a very large guide word size you could narrow down the search for a particular word to the exact page. Narrowing this range down means less of the lexical database neads to be read and searched, but it also means the index gets larger. DICPLN:(Dictionary Page Length) With a small page size there are more pages total, which means more guide words, which means a larger index, but it also means it may be possible to further narrow the range of pages wherein a target word must lie. This means less linear searching of pages for the target word. However, determining the range of pages wherein a word must lie is also dependent upon the size of the guide word used (INDSWD). Conversely, a large page size means less guide words, a smaller index size, and faster searching of the index. Changing the page size from 512 to 1024 will cut the size of the index in half. However, when the range of pages is determined, those pages will have to be searched to find the target word. Recommendations: The index guide word size should be as large as possible and the page length should be as small as possible so that the position of most words in the lexical database can be narrowed down as much as possible. The increase in size of the index is small and only affects the total size of the file EDX_DICTIONARY.DAT, of which the index accounts for only about 1%. It does not adversely affect the process since none of the EDX dictionary uses up virtual page file quota (pgflquo). (It is mapped into memory as process private non-modifiable disk section using $CRMPSC). As such we have set the dictionary page size 512 bytes (1 pagelet), and the index guide word length to 6 which seems to be long enough so that there are few instances of two guide words being the same. PFC:(Page Fault Cluster Size argument to $CRMPSC) This is the 12th and last argument to the system service routine $CRMPSC. This determines how many pagelets (512 byte pages) get swapped in each time a page fault occurs accessing the lexical database. A small number here means when a page fault occurs less time is spent reading in the cluster of pages, but more page faults overall are likely to occur. A large number here means when a page fault does occur we page in a lot of pages. Page faulting in 1 page at a time 500 times is a lot slower than paging in 500 pages at once. Tests also show that small PFC size can cause the spelling checker to go twice as slow. It is also intuitive that if we are spell checking a document, we will make heavy use of the entire lexical database during that time and it would be nice to page in as much as possible up to the limit of the users working set quota. On AXP the real page size is either 8K, 16K, 32K, or 64K. We have chosen the page size to be 64K * 4 = 500 pagelets. OPTIMIZING THE COMMON WORD LIST: The common word list is searched first for a match before searching the main lexical database. Common words which occur often are thus handled quickly. A longer common word list means a higher chance of matching whatever the target word is, thus skipping the longer process of searching The main lexical database. However, a longer common word list means more time spent searching the common word list. Examining a reference book which lists words according to frequency of use is helpful in determining which words should be included in a common word list, and how long The list should be. It turns out that The first few words at The top of The list are used quite frequently, with The number one word at The very top of The list being used nearly 3 times as often as The second word on The list. (And by now you may have guessed that The number one most frequently used word in The English language is The word 'THE'). Probably a list containing the first 10 most commonly used words would be as effective in speeding up a spelling checker as a word list of the first 100 most commonly used words. All characters in the dictionary file EDX_DICTIONARY.DAT are in lowercase. TESTING: To test the spell checking code try spell checking it's own dictionary. If it can't spell check that something's wrong. Also need to try some ('s) words. Also try spell checking the user's personal dictionary. ---------------------------------------------------------------------------*/ /* Constants */ #define BLOCK_SIZE 512 /* Number of bytes in a block */ #define WDBUF_SIZE 80 /* Inword buffer */ #define DICVERNO 3 /* EDX Dictionary Version Number */ static const char DICID[8] = { DICVERNO, 'E', 'D', 'X', 'd', 'i', 'c', 't' }; /* GLOBAL SPELL CHECK VARIABLES */ #define WORD_COLUMN_LENGTH 20 static int gmode; /* guess mode (1=reversals,2=vowels,3=minus,4=plus,5=consonants,6=giveup) */ static int gsubmode; /* guess submode (letter we're currently replacing with) */ static int gof; /* guess column offset (character # in word working on) */ static char *newnode = 0; /* pointer to structure node, the new node added to the tree */ static int32 accept_tree = 0; /* header for accepted word tree */ static int32 savcor_tree = 0; /* header for saved corrections tree */ static char *dicptra; /* points to length-byte preceeding first word displayed on screen by dic_browse */ static char *dicptrz; /* points to length-byte following last word displayed on screen by dic_browse */ static char target_word[80]; /* word spelling checker is currently checking */ static int target_word_len; /* length of above */ static char dic_lwa[80]; /* copy of last misspelled word */ static int dic_lwl; /* last misspelled word length */ /* SPELL CHECKER GLOBAL VARIABLES */ static struct FAB dicfabio; static struct RAB dicrabio; static struct NAM dicnamio; static struct FAB dicfabmap; static struct FAB persdicfab; static struct RAB persdicrab; static char *cmnwdsptr = 0; /* Address of common words */ static char *dicindptr = 0; static int dic_loaded = FALSE; /* TRUE when EDX dictionary successfully loaded */ static char wdbuf[WDBUF_SIZE]; static int32 maprange[2] = { 0x200, 0x200 }; /* any program (P0) region address */ static struct { char *lexdba; /* Lexical Database Address (address range returned here as 2 longwords begin:end) */ char *lexend; /* ASSUME sizeof(lex) = longword */ } dic; static struct dichead_layout { char id[8]; /* header id */ int32 lexvbn; int32 lexvbn_h; /* Lexical Database Virtual Block Number (VBN it starts at) */ int32 lexbln; int32 lexbln_h; /* Lexical Database Block Length (length in 512 byte blocks) */ int32 indvbn; int32 indvbn_h; /* Index Length (in bytes) */ int32 indlen; int32 indlen_h; /* Index Length (in bytes) */ int32 indswd; int32 indswd_h; /* Index Size of guide Word (in bytes) */ int32 dicpln; int32 dicpln_h; /* Dictionary Page Length (in bytes) */ int32 cwdvbn; int32 cwdvbn_h; /* Commonwords Virtual Block Number (VBN it starts at) */ int32 cwdlen; int32 cwdlen_h; /* Commonwords Length (in bytes) */ int32 cwdmln; int32 cwdmln_h; /* Commonwords Maximum Length (in bytes) */ } dichead; /*---------------------------------------------------------------------------*/ void edx_spell() { int status; if (!dic_loaded) /* if not spell dic already mapped */ { /* then map it into memory */ status = spell_init(); /* Initialize spelling checker */ if ( !(status & STS$M_SUCCESS) ) /* Error initializing dictionary */ { fmtoutstr( 1, &status ); return; } } switch (*incode_ptr) { case INCODE_SPELL_DIC_BROWSE_PA: /* 1 = Dictionary browse previous page */ case INCODE_SPELL_DIC_BROWSE_RW: /* 2 = Dictionary browse using word */ case INCODE_SPELL_DIC_BROWSE_PZ: dic_browse(); break; /* 3 = Dictionary browse next page */ case INCODE_SPELL_TEXTLINE: spell_textline(&instr_desc); break; /* 4 = Spell textline */ case INCODE_SPELL_GUESS: spell_guess(); break; /* 5 = Spell guess */ case INCODE_SPELL_ACCEPT_WORD: spell_accept_word(); break; /* 6 = Accept word (add to accepted word list) */ case INCODE_SPELL_ADD_PERSDIC: add_persdic(); break; /* 7 = Add word to personal dictionary */ case INCODE_SPELL_DUMP_COMMONWORDS: dump_commonwords(); break; /* 8 = Dump commonword list */ case INCODE_SPELL_SAVE_CORRECTION: save_correction(&instr_desc); break; /* 9 = Save misspelled word and its correction */ default: edx_signal(1, byref(EDX__UNKNCODE) ); break; /*Unknown item code */ } } /*--------------------------------------------------------------------------- .SUBTITLE SPELL_INIT Functional Description: Initializes the EDX dictionary. Opens all necessary files, reads in all necessary data. On error prints error and returns error status. Uses system service routine $CRMPSC to map the dictionary straight into memory. This method does not use up any user pgflquo quota. Calling Sequence: status = spell_init(); Outputs: status Outline: 1. FABs, RABs, NAMs, etc initialized. 2. The EDX dictionary database file EDX_DICTIONARY.DAT is opened and connected to. 3. The rest of the file is mapped into memory using $CRMPSC. 4. Pointers to the index and common words are set. (Pointer to the lexical database itself was set by call to $CRMPSC) 5. User's personal dictionary file, if one is found, is opened and the words there are inserted into the binary tree of accepted words. ---------------------------------------------------------------------------*/ int spell_init() { int status; int sig_array[6]; char filename[NAM$C_MAXRSS]; struct dsc$descriptor_s wdbuf_desc = { 0, DSC$K_DTYPE_T, DSC$K_CLASS_S, wdbuf }; edx_signal(1, byref(EDX__DICLOAD) ); /* Say "Loading dictionary" */ /* Initialize DICNAMIO */ dicnamio = cc$rms_nam; /* initialize NAM */ dicnamio.nam$l_esa = (char *) filename; /* Expanded file name string */ dicnamio.nam$b_ess = NAM$C_MAXRSS; /* Expanded file name string size */ /* Initialize DICFABIO */ dicfabio = cc$rms_fab; /* initialize FAB */ dicfabio.fab$l_fna = (char *) "EDX_DICTIONARY"; /* file name address */ dicfabio.fab$b_fns = LENGTH("EDX_DICTIONARY"); /* file name length */ dicfabio.fab$l_dna = (char *) "SYS$LIBRARY:EDX_DICTIONARY.DAT"; /* default file name address */ dicfabio.fab$b_dns = LENGTH("SYS$LIBRARY:EDX_DICTIONARY.DAT"); /* default file name length */ dicfabio.fab$l_nam = &dicnamio; /* NAM block address */ dicfabio.fab$b_fac = FAB$M_BIO | FAB$M_GET; /* File Access options = Block I/O, GET */ dicfabio.fab$b_shr = FAB$M_SHRGET; /* share options */ /* Initialize DICRABIO */ dicrabio = cc$rms_rab; /* initialize RAB */ dicrabio.rab$l_fab = &dicfabio; /* address of FAB */ /* Initialize DICFABMAP */ dicfabmap = cc$rms_fab; /* initialize FAB */ dicfabmap.fab$l_fna = (char *) "EDX_DICTIONARY"; /* file name address */ dicfabmap.fab$b_fns = LENGTH("EDX_DICTIONARY"); /* file name length */ dicfabmap.fab$l_dna = (char *) "SYS$LIBRARY:EDX_DICTIONARY.DAT"; /* default file name address */ dicfabmap.fab$b_dns = LENGTH("SYS$LIBRARY:EDX_DICTIONARY.DAT"); /* default file name length */ dicfabmap.fab$l_fop = FAB$M_UFO; /* User File Open */ dicfabmap.fab$b_fac = FAB$M_GET; /* UPI must be set says the book */ dicfabmap.fab$b_shr = FAB$M_SHRGET | FAB$M_UPI; /* share options */ dicfabmap.fab$b_rtv = -1; /* keep all pointers */ /* Initialize PERSDICFAB */ persdicfab = cc$rms_fab; /* initialize FAB */ persdicfab.fab$l_fna = (char *) "EDXPERSDIC"; /* file name address */ persdicfab.fab$b_fns = LENGTH("EDXPERSDIC"); /* file name length */ persdicfab.fab$l_dna = (char *) "SYS$LOGIN:EDXPERSDIC.DAT"; /* default file name address */ persdicfab.fab$b_dns = LENGTH("SYS$LOGIN:EDXPERSDIC.DAT"); /* default file name length */ persdicfab.fab$b_fac = FAB$M_GET; /* File Access options = Block I/O, GET */ persdicfab.fab$b_shr = FAB$M_SHRGET; /* share options */ /* Initialize PERSDICRAB */ persdicrab = cc$rms_rab; /* initialize RAB */ persdicrab.rab$l_fab = &persdicfab; /* address of FAB */ persdicrab.rab$l_ubf = (char *) wdbuf; /* address of buffer */ persdicrab.rab$w_usz = WDBUF_SIZE; /* buffer size */ /* OPEN AND CONNECT TO EDX_DICTIONARY */ status = sys$open(&dicfabio); if (status & STS$M_SUCCESS) status = sys$connect(&dicrabio); if ( !(status & STS$M_SUCCESS) ) { lib$put_output(&bell_desc); /* Ring terminal bell */ sig_array[0] = EDX__ERROPENDIC; /* error opening dictionary file message */ sig_array[1] = 2; /* 2 FAO args */ sig_array[2] = dicnamio.nam$b_esl; /* filename size */ sig_array[3] = dicnamio.nam$l_esa; /* filename address */ sig_array[4] = status; /* Error status */ sig_array[5] = 0; /* 0 FAO args */ edx_signal( 6, sig_array ); return(EDX__ERROPENDIC); } /* READ IN DICTIONARY HEADER */ dicrabio.rab$l_bkt = 1; /* Block number to read */ dicrabio.rab$l_ubf = &dichead; /* Buffer to put it all in */ dicrabio.rab$w_usz = sizeof(dichead); /* Buffer size = 1 BLOCK */ status = sys$read(&dicrabio); /* read in dictionary header block */ if ( !(status & STS$M_SUCCESS) ) { sig_array[0] = EDX__SPLINITERR; /* spell initialization error */ sig_array[1] = 0; /* 0 FAO args */ sig_array[2] = status; /* error status */ sig_array[3] = 0; /* o FAO args */ edx_signal( 4, sig_array ); return(EDX__SPLINITERR); } /* CHECK VALIDITY OF HEADER */ if (memcmp(&dichead.id,DICID,8)) /* = 0 if they match, which = FALSE */ { if (!memcmp(&dichead.id+1,&DICID[1],3)) /* version #2 only had ".EDX" */ { sig_array[0] = EDX__DICVERSERR; /* dictionary version error. */ sig_array[1] = 2; /* 2 FAO arguments */ sig_array[2] = (int) dichead.id[0]; /* actual version number */ sig_array[3] = DICVERNO; /* required version number */ edx_signal( 4, sig_array ); return(EDX__DICVERSERR); } else { sig_array[0] = EDX__DICHEADERR; /* dictionary header error. It's not the EDX_DICTIONARY file */ sig_array[1] = 2; /* 2 FAO args */ sig_array[2] = dicfabio.fab$b_fns; /* filename size */ sig_array[3] = dicfabio.fab$l_fna; /* filename address */ edx_signal( 4, sig_array ); return(EDX__DICHEADERR); } } /* MAP LEXICAL DATABASE INTO MEMORY */ sys$close(&dicfabio); /* close it for I/O */ status = sys$open(&dicfabmap); /* open it for mapping */ if (status & STS$M_SUCCESS) status = sys$crmpsc( &maprange, /* inadr */ &dic, /* retadr (array by reference) */ 0, /* acmode */ SEC$M_EXPREG, /* flags */ 0, /* gsdnam */ 0, /* ident */ 0, /* relpag */ dicfabmap.fab$l_stv, /* chan */ 0, /* pagcnt */ dichead.lexvbn, /* vbn */ 0, /* prot */ 500 ); /* pfc */ if ( !(status & STS$M_SUCCESS) ) { lib$put_output(&bell_desc); /* Ring terminal bell */ sig_array[0] = EDX__ERRMAPDIC; /* error mapping dictionary file message */ sig_array[1] = 0; /* 0 FAO args */ sig_array[2] = status; /* error code */ sig_array[3] = 0; /* 0 FAO args */ edx_signal( 4, sig_array ); return(EDX__ERRMAPDIC); } /* SET POINTERS TO INDEX AND COMMON WORDS */ cmnwdsptr = dic.lexdba + (dichead.cwdvbn - dichead.lexvbn)*BLOCK_SIZE; dicindptr = dic.lexdba + (dichead.indvbn - dichead.lexvbn)*BLOCK_SIZE; dic_loaded = TRUE; /* successfully loaded enough so we can spell check */ /* OPEN AND READ IN THE USER'S PERSONAL DICTIONARY FILE */ status = sys$open(&persdicfab); if (status & STS$M_SUCCESS) status = sys$connect(&persdicrab); if (status != RMS$_FNF) { if (status & STS$M_SUCCESS) { /* GO THROUGH THE USERS PERSONAL DICTIONARY, ADD THE WORDS TO OUR TREE */ while ( sys$get(&persdicrab) & STS$M_SUCCESS ) { wdbuf_desc.dsc$w_length = persdicrab.rab$w_rsz; spell_textline(&wdbuf_desc); /* this trims, lowercases, and sets word for inclusion in accepted word tree */ spell_accept_word(); /* add word to accepted word list */ } } else { sig_array[0] = EDX__PERSDICERR; /* error opening personal dictionary */ sig_array[1] = 2; /* 2 FAO args */ sig_array[2] = persdicfab.fab$b_fns; /* filename size */ sig_array[3] = persdicfab.fab$l_fna; /* filename address */ sig_array[4] = status; /* Error status */ sig_array[5] = 0; /* 0 FAO args */ edx_signal( 6, sig_array ); /* if there's an unexpected error accessing the user's personal dictionary then signal the error and continue. As long as we got the EDX_DICTIONARY.DAT file we can spell check */ } } sys$close(&persdicfab); /* close user's personal dictionary */ edx_signal(1, byref(EDX__DICLOADED) ); /* Say "Dictionary Loaded" */ return(SUCCESS); } /*--------------------------------------------------------------------------- .SUBTITLE SPELL_TEXTLINE Functional Description: Checks the spelling of each word in the input string Calling Sequence: spell_textline(&inbuf_desc) Argument inputs: inbuf_desc - address of descriptor of string containing words to check (usually INSTR) Outputs: OUTSTR = characters 1-9 is return status value LIB$_NORMAL if all words in line spelled correctly LIB$_NOTFOU if a word in line was spelled incorrectly characters 10-14 is decimal value of offset from start of instr where misspelled word begins character 15 is space character characters 16-20 is decimal value of length of misspelled word. Outline: 2. The next word in inbuf_desc string is parsed off a. inbuf is searched for the start of a word. The start of a word is any character {A...Z,a...z}. b. inbuf is searched for the end of the word. The end of the word is any character other than {A...Z,a...z} and the appostrophie ("'") character. A special check is made to handle the appostrophie. Words line "we're" or "you'd" are accepted as is. Words which have a trailing "'s" such as "Mark's" or "Saturday's" have the "'s" trimmed off. We ignore quotes which occur at the end of a word with no letters following. When a single quote character "'" is encountered: i. If we are at the end of the line, the quote is rejected. ii. If the next character following the quote is not a letter (A-Z,a-z), the quote is rejected. iii. If the next character following the quote is a "S" or a "s", the word is accepted without the trailing "'s" the pointer is advanced to the next character following the "'s". 3. A dictionary lookup search is made for a match to the resulting parsed off word. 4. Loop back to step 2 until all words in INSTR have been checked. ---------------------------------------------------------------------------*/ void spell_textline( struct dsc$descriptor_s *inbuf_desc) { int i; int wdlen; /* word length */ int status; int delta; /* number of chars to skip before looking for next word */ int sig_array[3]; char *inend; /* points to one char after end of inbuf */ char *wdptr; /* word pointer */ char *wdbeg; /* word begin */ char *wdend; /* word end */ char corout[BUFLEN+8]; $DESCRIPTOR(corout_desc,corout); char faobuf[BUFLEN]; $DESCRIPTOR(faobuf_desc,faobuf); char retbuf[BUFLEN]; $DESCRIPTOR(retbuf_desc,retbuf); struct dsc$descriptor_s word_desc; $CONST_DESCRIP(faosplout_desc,"!5UL !5UL"); /* THIS HAS CHANGED FROM 3 */ $CONST_DESCRIP(faocorout_desc,"!5UL !5UL !AC"); /* THIS HAS CHANGED FROM 3 */ wdptr = inbuf_desc->dsc$a_pointer; inend = inbuf_desc->dsc$a_pointer + inbuf_desc->dsc$w_length; delta = 0; do { /* SEARCH FOR START OF WORD */ /* Find first alphabetic character in set {A...Z,a...z} or end-of-line */ for ( wdptr += delta, delta = 0; (wdptr < inend) && !isalpha(*wdptr); /* check for out of bounds before checking character */ ++wdptr ); if (wdptr == inend) /* end of line found. No more words on this line */ { fmtoutstr(1, &LIB$_NORMAL ); return; } wdbeg = wdptr; /* save start of word */ /* SEARCH FOR END OF WORD */ for (;;) { /* span over alphabetic {A...Z,a...z} until non-alphabetic or end-of-line found. */ for ( ; (wdptr < inend) && isalpha(*wdptr); ++wdptr ); /* Check for out of bounds before checking character */ if (wdptr == inend) break; /* if end-of-line then accept word */ if (*wdptr != '\'') break; /* if non-alphabetic and not single quote (') then accept word. (MOST COMMON LOOP EXIT with *wdptr == SPACE ) */ if (++wdptr == inend){--wdptr; break;} /* If no next character (end-of-line) then accept word without (') */ if (!isalpha(*wdptr)){--wdptr; break;} /* if apostrophe not followed by alphabetic characer then accept word without (') */ if (*wdptr == 's' || *wdptr == 'S'){--wdptr; delta = 2; break;} /* accept word without ('S) delta=2 -> skip over 'S before searching for next word on line */ /* else we have apostrophe followed by alphabetic non-S char. As in "you're", "we'll", etc. These are checked as whole words. Continue loop searching for end-of-word */ } /* END SEARCH FOR end-of-word LOOP */ /* On above loop exit: wdptr points to character after end of word wdbeg points to beginning of word length of word is wdptr - wdbeg delta = # of chars to skip past word end before searching for beginning of next word on line. */ wdend = wdptr; wdlen = wdend - wdbeg; status = dic_lookup_word( wdlen, wdbeg ); /* see if word is in dictionary */ } while ( (status & STS$M_SUCCESS) ); /* FOUND MISSPELLED WORD */ /* Move misspelled word to storage place (DIC_LWA) and Lowercase the string */ for ( wdptr = wdbeg, i = 0; wdptr < wdend && i < BUFLEN; /* save only as much as we can */ ++wdptr, ++i ) dic_lwa[i] = _tolower(*wdptr); dic_lwl = i; gmode = 1; /* reset guess mode pointers */ gsubmode = gof = 0; /* set up response string to EDX */ /* check misspelled word list for possible suggested correction */ word_desc.dsc$w_length = dic_lwl; word_desc.dsc$b_class = DSC$K_CLASS_S; word_desc.dsc$b_dtype = DSC$K_DTYPE_T; word_desc.dsc$a_pointer = dic_lwa; if ( lib$lookup_tree(&savcor_tree,&word_desc,compare_node,&newnode) & STS$M_SUCCESS ) { /* correction word found, make response and include it. */ sys$fao(&faocorout_desc, /* ctrstr without suggested correction (by descriptor) */ &corout_desc.dsc$w_length, /* (write) outlen */ &corout_desc, /* outbuf (by descriptor) */ wdbeg - inbuf_desc->dsc$a_pointer,/* offset into textline where misspelled word starts */ wdlen, /* length of misspelled word */ newnode + 12 + *(int16 *)(newnode+10) ); /* address of ASCIC string which is suggested correction */ } else { /* correction word not found, make response without it */ sys$fao(&faosplout_desc, /* ctrstr without suggested correction (by descriptor) */ &corout_desc.dsc$w_length, /* (write) outlen */ &corout_desc, /* outbuf (by descriptor) */ wdbeg - inbuf_desc->dsc$a_pointer,/* offset into textline where misspelled word starts */ wdlen); /* length of misspelled word */ } /* final output string */ sig_array[0] = status; sig_array[1] = corout_desc.dsc$w_length; sig_array[2] = corout; fmtoutstr( 3, &sig_array ); } /*============================================================================= .SUBTITLE DIC_LOOKUP_WORD Functional Description: Searches the EDX dictionary for a given word Calling Sequence: status = dic_lookup_word(wdlen, wdbeg); Argument inputs: wdlen - length of word to search for (by value) wdbeg - pointer to start of word array Outputs: status = LIB$_NORMAL - word was found = LIB$_NOTFOU - word was not found Outline: 1. The input word is copied to target_word buffer and lowercased. 2. The dictionary common word list is searched for the word. 3. The main lexical database is searched for the word. 4. The list of accepted words is searched for a match. ---------------------------------------------------------------------------*/ int dic_lookup_word(int wdlen, char *wdbeg) { int i; int low; /* lower bound page # */ int high; /* upper bound page # */ char *wdend; /* word pointer */ char *wdptr; /* word pointer */ char *dptr; /* pointer into dictionary into word */ char *lbptr; /* pointer to length-byte of current word */ char *tptr; /* pointer into target_word */ char *endrange; struct dsc$descriptor_s target_word_desc; /* NOTE: pages referred to are edx_dictionary pages of size DICPLN */ /* SETUP_DICWORD */ /* Move misspelled word to storage place, lowercase, and blank pad to INDSWD in length (so we can compare it with guide words) */ if (wdlen == 0) return(LIB$_NORMAL); /* accept zero length word as OK */ if (wdlen > BUFLEN) return(LIB$_NOTFOU); /* Word over 255 chars long. Can't possibly be a word. User probably doesn't want us to stop on it anyway. */ wdend = wdbeg + wdlen; /* wdend -> char after last char of word */ for ( wdptr = wdbeg, i = 0; wdptr < wdend; ++wdptr, ++i ) target_word[i] = _tolower(*wdptr); target_word_len = wdlen; for ( ; i < dichead.indswd; ++i ) /* BLANK PAD TO INDSWD LENGTH */ target_word[i] = SPACE; /* SEARCH COMMON WORD LIST FOR MATCH */ if (target_word_len <= dichead.cwdmln) /* skip if target_word is too long to be in commonword list */ { endrange = cmnwdsptr + dichead.cwdlen; /* end of commonwords */ lbptr = cmnwdsptr; /* start at beginning of common words */ while (lbptr < endrange) /* still in range of dictionary we're searching */ { if (*lbptr == 0x00) break; /* End of Lexical Database */ if (*lbptr == target_word_len) /* check if word lengths match first */ { for ( tptr = target_word + target_word_len -1, /* start tptr at last char of target_word */ dptr = lbptr + target_word_len; /* start dptr at last char of word in dictionary */ tptr >= target_word && *tptr == *dptr; /* while chars match up to beginning of word */ --tptr, --dptr); /* move back a char */ if (tptr < target_word) return(LIB$_NORMAL); /* word found */ } lbptr += *lbptr + 1; /* move to next word */ } } /* SEARCH MAIN DICTIONARY FOR MATCH */ binsrch_maindic( &low, &high, &target_word ); /* Linear search dictionary pages for match to target word. Compare found word with target word starting with last character and moving to front of word. We do this because we already expect the first few characters of the word to match if we're looking in the right area of the dictionary. */ endrange = dic.lexdba + high * dichead.dicpln; for ( lbptr = dic.lexdba + low * dichead.dicpln; *lbptr > 31; ++lbptr); /* find a length-byte */ while (lbptr < endrange) { if (*lbptr == 0x00) break; /* End of Lexical Database */ if (*lbptr == target_word_len) /* check if word lengths match first */ { for ( tptr = target_word + target_word_len -1, /* start tptr at last char of target_word */ dptr = lbptr + target_word_len; /* start dptr at last char of word in dictionary */ tptr >= target_word && *tptr == *dptr; /* while chars match up to beginning of word */ --tptr, --dptr); /* move back a char */ if (tptr < target_word) return(LIB$_NORMAL); /* word found */ } lbptr += *lbptr + 1; /* move to next word */ } /* DROP OUT BOTTOM IF WORD NOT FOUND IN MAIN DICTIONARY */ /* SEARCH ACCEPTED WORD LIST FOR MATCH */ target_word_desc.dsc$w_length = target_word_len; target_word_desc.dsc$b_class = DSC$K_CLASS_S; target_word_desc.dsc$b_dtype = DSC$K_DTYPE_T; target_word_desc.dsc$a_pointer = target_word; if ( lib$lookup_tree(&accept_tree,&target_word_desc,compare_node,&newnode) & STS$M_SUCCESS ) return(LIB$_NORMAL); /* WORD FOUND IN ACCEPTED WORD LIST */ else return(LIB$_NOTFOU); /* WORD NOT FOUND ANYWHERE. SORRY */ } /*--------------------------------------------------------------------------- .SUBTITLE BINSRCH_MAINDIC Functional Description: The index to the dictionary main lexical database is searched to determine the page range within which target_word must lie if it exists. Calling Sequence: binsrch_maindic( &low, &high, &target_word ); Argument inputs: target_word - character array of word to match, blank padded to dichead.indswd (by reference) Outputs: low - page number below which word would not reside (by reference) high - high page number above which word would not reside (by reference) Notes: The first page number is 0. ---------------------------------------------------------------------------*/ void binsrch_maindic( int *low, int *high, char *target_word ) { int cmp; int new; /* PREPARE FOR BINARY SEARCH */ *high = dichead.indlen/dichead.indswd - 1; *low = 0; /* BINARY SEARCH THE INDEX */ for (;;) { new = (*low + *high)/2; if (new == *low) break; /* exitloop when guess=lowb */ cmp = memcmp(target_word, dicindptr + new*dichead.indswd, dichead.indswd); if (cmp == 0) break; /* switch to linear search */ if (cmp > 0) *low = new; /* word is in higher half */ else *high = new; /* else word is in lower half */ }/*endloop */ /* NOW DO LINEAR SEARCH UP AND DOWN TO FIND TRUE PAGE BOUNDARIES */ /* FIRST LOOK TOWARD Z'S FOR NEW > TARGET_INDEX OR END OF DICTIONARY */ /* Set upper bound page #. NOTE: Search is to INCLUDE this page up to first length-byte. Word we are looking for may be at the end of prev page spilling over into this page. ALSO NOTE: if word is in very last page of dictionary past last guide word then we must increment high by one to include the last page. */ *high = dichead.indlen/dichead.indswd - 1; /* number of last page in dictionary */ while( (cmp = memcmp(target_word, dicindptr + new*dichead.indswd, dichead.indswd)) >= 0 && new != *high ) ++new; *high = new; /* Set upper bound page # */ if (cmp >= 0) ++*high; /* include very last page of dictionary if need be */ /* SEARCH FOR NEW < TARGET_INDEX OR BEGINNING OF DICTIONARY */ while( memcmp(target_word, dicindptr + new*dichead.indswd, dichead.indswd) <= 0 && new != 0 ) --new; *low = new; /* set lower bound page # */ } /*============================================================================= .SUBTITLE DIC_BROWSE Functional Description: Returns words from the dictionary for EDX to display in it's dictionary lookup buffer. This routine returns in OUSTSTR a very long string of length ROWS x COLUMNS which is to be broken up by EDX into pieces of length COLUMNS and displayed. We either do a lookup best match to given word, a display next page, or a display previous page, depending upon the value of R6. Calling Sequence: dic_browse(); Argument inputs: ....v....1....v....2....v....3....v....4....v....5....v....6... INSTR - "yyyyyyyyzzzzzzzzwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww... where yyyyyyyy is ascii for a hex longword indicating the number of rows in the DIC window to fill and zzzzzzzz is ascii for a hex longword indicating the number of columns in the DIC window to fill and wwwwwwwwwwww... is the word to best match if we are not doing a prev_page or next_page display. Implicit inputs: incodel - 1 display previous page 2 do best match word and display page 3 display next page Outputs: OUTSTR - a string of length ROWS x COLUMNS (yyyyyyyy x zzzzzzzz) containing all the best match words in alphabetical order and arranged so that the first n characters is to be put as row 1, the next n characters as row 2, etc... For typical values of 10 ROWS and 80 COLUMNS the output is displayed as 4 columns, each word a maximum length of 19 characters else the word overflows into the next column. Example final output display: word1 word11 word21 word31 word2 word12 word22 word32 word3 word13 word23 word33 word4 word14 word24 word34 word5 word15 word25 word35 word6 word16 word26 word36 word7 word17 word27 word37 word8 word18 word28 word38 word9 word19 word29 word39 word10 word20 word30 word40 The above typical example has 4 word columns and 10 rows. In the above example we'd try to make our best fit word word21. Implicit: Pointers DICPTRA and DICPTRZ are set. DICPTRZ points to the length-byte following the last word in the lexical database displayed on the screen. DICPTRA points to the length-byte preceeding the first word in the lexical database displayed. These pointers are used if the user requests to see the next or previous screen full of words in the dictionary. Outline: 1. INSTR is parsed, yyyyyyyy, zzzzzzzz, are converted to integers. 2. Depending on incodel we either call dic_browse_prev_page, dic_browse_word, or dic_browse_next_page (which is just calling dic_browse_fill passing it DICPTRZ) ---------------------------------------------------------------------------*/ void dic_browse() { unsigned int status; unsigned int incodel; unsigned int rows; unsigned int columns; struct dsc$descriptor_s numrows_desc = { 8, DSC$K_DTYPE_T, DSC$K_CLASS_S, instr_desc.dsc$a_pointer }; struct dsc$descriptor_s numcols_desc = { 8, DSC$K_DTYPE_T, DSC$K_CLASS_S, instr_desc.dsc$a_pointer+8 }; incodel = (*incode_ptr & 0x0000FFFF); status = ots$cvt_tz_l( &numrows_desc, &rows ); /* convert ascii hex to binary */ if ( !(status & STS$M_SUCCESS) ) lib$signal(status); /* signal error */ status = ots$cvt_tz_l( &numcols_desc, &columns ); /* convert ascii hex to binary */ if ( !(status & STS$M_SUCCESS) ) lib$signal(status); /* signal error */ switch (incodel) { case 1: /* 1 = display prev page */ dic_browse_prev_page( columns, rows ); break; case 2: /* 2 = lookup word. Use wwwwwwwwwww... word in INSTR */ dic_browse_word( instr_desc.dsc$w_length-16, /* length - 16 to skip over yyyyyyyyzzzzzzzz */ instr_desc.dsc$a_pointer+16, /* address of instr+16 (wwwwwwwwwwwwwww... word) */ columns, rows ); break; case 3: /* 3 = display next page */ dic_browse_fill( dicptrz, /* fill starting with this word */ columns, rows); break; default: /* Unknown item code */ edx_signal(1, byref(EDX__UNKNCODE) ); break; } } /*--------------------------------------------------------------------------- .SUBTITLE DIC_BROWSE_PREV_PAGE Functional Description: Starting at DICPTRA, counts backwards an appropriate number of words, and calls dic_browse_fill to do the rest given the starting point. Calling Sequence: dic_browse_prev_page( window_columns, window_rows ); Argument inputs: window_columns - number of characters across window display (width) window_rows - number of rows in window display to fill (height) Outline: 1. Count backwards N words (N = window_rows). In the above example these words will occupy word31 - word40, the right most column. 2. Count backwards M more words. In the above example these words will occupy word1 - word30. In general M is the number of words necessary to fill all the previous columns we have. M is calculated as follows: M = ( total_number_of_word_columns - 1 ) * window_rows ( if M < 0 then M = 0 ) where total_number_of_word_columns = INT( window_columns / WORD_COLUMN_LENGTH ) If a word is longer than WORD_COLUMN_LENGTH-1, then we count it as two words. For example, in the above example if word3 were longer than WORD_COLUMN_LENGTH-1, it would spill into the column for word13. Thus this word effectively takes up two word spaces. ---------------------------------------------------------------------------*/ void dic_browse_prev_page( int window_columns, int window_rows) { int n,m; int word_length; char *dptr; /* Step 1. Count N words backwards not looking at word length */ /* GO BACK N WORDS */ dptr = dicptra; for ( n = window_rows; n > 0; --n ) { if (dptr == dic.lexdba) break; /* don't back up any more if we're at the beginning of the database */ for( --dptr; /* backup one char to get off length-byte */ *dptr > 31; /* then while not on length-byte */ --dptr ); /* move back a char */ } /* Step 2. Count M words backwards looking at word length if word length >= word_column_length-1 then count as two words total_number_of_word_columns = INT( window_columns / WORD_COLUMN_LENGTH ) M = ( total_number_of_word_columns - 1 ) * window_rows */ for ( m = ( (window_columns/WORD_COLUMN_LENGTH) - 1 ) * window_rows; m > 0 /* Count M words backwards */ && dptr > dic.lexdba; /* check for at beginning of dictionary */ --m) /* GO BACK ONE WORD (or WORD_COLUMN_LENGTH-1 chars, whichever comes first) */ for ( word_length = 0, --dptr; /* Start with zero word_length */ *dptr > 31 /* While not found length-byte (separating words) */ && word_length != WORD_COLUMN_LENGTH-1;/* and word still fits in column */ ++word_length, --dptr ); /* Increment word_length, backup a char */ /* IN CASE WE ENDED UP IN THE MIDDLE OF A LONG WORD, MOVE FORWARD TO THE FIRST LENGTH-BYTE. (USUALLY WE'RE ALREADY ON IT AT THIS POINT) */ for ( ; *dptr > 31; ++dptr); /* move forward to first length-byte (probably already on it) */ /* NOW CALL DIC_BROWSE_FILL TO FILL OUTSTR WITH WORDS FROM DICTIONARY STARTING AT dptr */ dic_browse_fill( dptr, window_columns, window_rows ); } /*------------------------------------------------------------------------- .SUBTITLE DIC_BROWSE_WORD Functional Description: Accepts a word defined by inwdlen, inwdbeg. Searches in the dictionary for the best match to the given word. Counts backwards an appropriate number of words, and calls dic_browse_fill to do the rest given the starting point. Calling Sequence: dic_browse_word( inwdlen, inwdbeg, window_columns, window_rows ); Argument inputs: number of arguments = 4 inwdlen = length of string containing word to best match inwdbeg = pointer to string containing word to best match window_columns = number of characters across window display (width) window_rows = number of rows in window display to fill (height) Outline: 1. The given word is copied over to local storage, lowercased, and blank padded to INDSWD in length. 2. The index to the dictionary main lexical database is searched to determine the page range within which the word must lie if it exists. 3. Search range of dictionary pages for match to word. If no match found search again for word(1:length-1). If no match found search for word(1:length-2), and so on until we reach search for word(1:0), which guarantees a match. 4. Find best match word. Search forwards until current_word > target_word. 5. Count backwards N words. In the example given in DIC_BROWSE, these words will occupy word11 - word20, the second column. N = window_rows. 6. Count backwards M more words. In the above example these words will occupy word1 - word10. In general M is the number of words necessary to fill all the previous columns we have. M is calculated as follows: M = ( INT( total_number_of_word_columns / 2 ) - 1 ) * window_rows ( if M < 0 then M = 0 ) where total_number_of_word_columns = INT( window_columns / WORD_COLUMN_LENGTH ) If a word is longer than WORD_COLUMN_LENGTH-1, then we count it as two words. For example, in the above example if word3 were longer than WORD_COLUMN_LENGTH-1, it would spill into the column for word13. Thus this word effectively takes up two word spaces. 7. Call DIC_BROWSE_FILL to fill outstr with words beginning at lbptr. ---------------------------------------------------------------------------*/ void dic_browse_word( int inwdlen, char *inwdbeg, int window_columns, int window_rows ) { /* NOTE: pages referred to are edx_dictionary page numbers of size DICPLN */ int i, m, n; int low; /* lower bound page # */ int high; /* upper bound page # */ int target_len; /* length of target word */ int word_length; /* word length */ char *wdptr; /* word pointer */ char *wdend; /* end of word pointer */ char *dptr; /* pointer into dictionary into word */ char *tptr; /* pointer into target_word */ char *lbptr; /* pointer into current word */ char *lbbeg; /* start here */ char *endrange; /* STEP 1 SETUP_DICWORD Move misspelled word to storage place, lowercase, blank pad to DICSWD chars */ wdend = inwdbeg + inwdlen; /* wdend -> char after last char of word */ for ( wdptr = inwdbeg, i = 0; wdptr < wdend; ++wdptr, ++i ) target_word[i] = _tolower(*wdptr); target_word_len = inwdlen; for ( ; i < dichead.indswd; ++i ) /* BLANK PAD TO INDSWD LENGTH */ target_word[i] = SPACE; /* STEP 2 */ /* SEARCH MAIN DICTIONARY FOR MATCH */ binsrch_maindic( &low, &high, &target_word ); /* STEP 3 LINEAR SEARCH DICTIONARY PAGES FOR BEST MATCH TO TARGET WORD Compare found word with target word for match of first n characters starting with n=target_word_length character and moving backwards towards front of word. We do this because we already expect the first few characters of the word to match if we're looking in the right area of the dictionary. (target_len = n) */ endrange = dic.lexdba + high * dichead.dicpln; for ( lbbeg = dic.lexdba + low * dichead.dicpln; *lbbeg > 31; ++lbbeg); /* find a length-byte to start on */ /* lbbeg now points to length-byte */ /* Search for target-word, target-wor, target-wo, target-w, etc... until match */ for (target_len = target_word_len; target_len > 0; /* (if n reaches 0 then dictionary doesn't have any words which start with this letter) */ --target_len) { for( lbptr = lbbeg; /* start here */ lbptr < endrange && *lbptr != 0x00; /* don't go past endrange or past end of lexical database */ lbptr += *lbptr + 1) /* save last word, move to next word */ { if (*lbptr >= target_len) /* word must be at least n chars long else no match */ { for ( tptr = target_word + target_len -1, /* start tptr at n'th char of target_word */ dptr = lbptr + target_len; /* start dptr at n'th char of word in dictionary */ tptr >= target_word && *tptr == *dptr; /* while chars match up to beginning of word */ --tptr, --dptr); /* move back a char */ if (tptr < target_word) goto match_found; /* match found, exit loops */ }/*test for match of first n chars*/ }/*try all words in range [lbbeg:endrange]*/ }/*try matching first n, n-1, n-2,... chars until match*/ ++target_len; /* (for loop decremented target_len before terminating loop) */ lbptr = lbbeg; /* (for loop advanced lbptr to next word before terminating loop) */ match_found: /* STEP 4 At this point we have lbptr pointing to first word in dictionary which matches first target_len chars. (There is no word which matches first target_len + 1 chars). Now find best closest match of next character for which there is no exact match. Next character is either next character of same word or length-byte of next word (possibly NULL if we're at the end of the last word in the dictionary). Search forward word by word until current dictionary word > target_word (need only check first target_len+1 chars). */ if (target_len < target_word_len) /* if we didn't get an exact match... */ { lbbeg = lbptr; /* last lbptr */ while ( lbptr < endrange && *lbptr != 0x00 /* don't go past endrange or past end of lexical database */ && string_compare( (int)*lbptr, /* length of dictionary word (may be less than 'length to match' below) */ lbptr+1, /* start of dictionary word */ target_len+1, /* length to match */ target_word ) /* word to match */ <= 0 ) { lbbeg = lbptr; /* save pointer to this word */ lbptr += *lbptr + 1; /* move to next word */ } lbptr = lbbeg; /* (while loop advanced lbptr to next word before terminating loop) */ } /* STEP 5 At this point, lbptr points to length-byte of word we seek. Count N words backwards not looking at word length. (N = window_rows) */ for ( n = window_rows; n > 0; --n ) { if (lbptr == dic.lexdba) break; /* don't back up any more if we're at the beginning of the database */ for( --lbptr; /* backup one char to get off length-byte */ *lbptr > 31; /* then while not on length-byte */ --lbptr ); /* move back a char */ } /* STEP 6 Count M words backwards looking at word length if word length >= word_column_length-1 then count as two words total_number_of_word_columns = INT( window_columns / WORD_COLUMN_LENGTH ) M = ( total_number_of_word_columns/2 - 1 ) * window_rows */ for ( m = ( (window_columns/WORD_COLUMN_LENGTH)/2 - 1 ) * window_rows; m > 0 /* Count M words backwards */ && lbptr > dic.lexdba; /* check for at beginning of dictionary */ --m) /* GO BACK ONE WORD (or WORD_COLUMN_LENGTH-1 chars, whichever comes first) */ for ( word_length = 0, --lbptr; /* Start with zero word_length, backup one char off length-byte */ *lbptr > 31 /* While not found length-byte (separating words) */ && word_length != WORD_COLUMN_LENGTH-1;/* and word still fits in column */ ++word_length, --lbptr ); /* Increment word_length, backup a char */ /* IN CASE WE ENDED UP IN THE MIDDLE OF A LONG WORD, MOVE FORWARD TO THE FIRST LENGTH-BYTE. (USUALLY WE'RE ALREADY ON IT AT THIS POINT) */ for ( ; *lbptr > 31; ++lbptr); /* move forward to first length-byte (probably already on it) */ /* STEP 7 NOW CALL DIC_BROWSE_FILL TO FILL OUTSTR WITH WORDS FROM DICTIONARY STARTING AT lbptr */ dic_browse_fill( lbptr, window_columns, window_rows ); } /*-------------------------------------------------------------------------- .SUBTITLE DIC_BROWSE_FILL Functional Description: Fills OUTSTR with words from dictionary starting at dptr address into lexical database. Calling Sequence: dic_browse_fill( dptr_param, window_columns, window_rows ); Argument inputs: dptr_param - address in dictionary lexical database to start at. This pointer MUST point to a length-byte (the byte preceding each word giving the length of the word. Identified by being less than ASCII 32.) window_columns - number of characters across window display (width) (by value) window_rows - number of rows in window display to fill (height) (by value) Outline: Create temporary buffer for OUTSTR and fill it in. We fill in the words starting with the first (left most) column, working our way down that column to the bottom. Then proceeding with the next column until all the columns are filled. For each word we calculate the offset into OUTSTR as follows: offset = row_number * nchars_per_row + word_column_number * word_column_length where row_number goes from 0 to window_rows-1 and word_column_number goes from 0 to number_of_word_columns-1 with number_of_word_columns = INT( window_columns/WORD_COLUMN_LENGTH ) Check to see if the word in the previous column is overflowing into the cell we were going to place our current word. If so we move down the column to the next cell and try again. If working on the last (right most) column then we check to see if the word we are inserting is longer than will fit on the screen. If so we truncate it and replace the last character with a "." EDX DUMP_DICTIONARY calls this to get the next word from the dictionary. It calls specifying rows=1, columns=0. If columns=0 is specified, then one word (the next word in line) is placed in the output buffer and the output buffer length set to 32. ---------------------------------------------------------------------------*/ #define MAX_WORD_SIZE 32 /* maximum size of word in dictionary */ void dic_browse_fill( char *dptr_param, int window_columns, int window_rows ) { int word_column_number, row_number, offset, outbufsize, sig_array[3]; char *dptr; char *outbuf; /* output buffer */ /* CHECK THAT DPTR POINTS TO LENGTH-BYTE. ERROR IF NOT. */ if (*dptr_param > 31) { edx_signal(1, byref(EDX__DBFDPTRLB) ); fmtoutstr( 1, byref(EDX__DBFDPTRLB) ); return; } dicptra = dptr = dptr_param; /* set DICPTRA (global) and DPTR (local) */ outbufsize = window_rows * window_columns + MAX_WORD_SIZE; outbuf = (char *) malloc(outbufsize); /* get a temporary buffer */ memset( outbuf, ' ', outbufsize ); /* blank fill our OUTSTR buffer */ /* LOOP ACROSS ALL WINDOW WORD COLUMNS */ word_column_number = 0; do /* if window_columns=0 we do one word. For dump_dictionary */ { /* LOOP DOWN ALL WINDOW ROWS (in this word_column) */ if (*dptr == 0x00) break; /* test for end of dictionary */ for (row_number = 0; /* Start at first row of new column */ *dptr != 0x00 /* check for end of dictionary */ && row_number < window_rows; /* loop down all window_rows */ ++row_number ) { offset = window_columns*row_number + word_column_number*WORD_COLUMN_LENGTH; /* offset into buffer */ if (word_column_number == 0) /* if first column then we know slot is free */ { memcpy((outbuf+offset),dptr+1,*dptr); /* insert word into OUTSTR */ dptr += *dptr+1; /* move to next word */ } else { /* CHECK THAT SLOT IS EMPTY */ if ( *(outbuf+offset-1) == SPACE ) /* test previous character for blank */ { /* if not then previous word on this line */ /* is too long. We must move down to next column */ /* ADD WORD TO OUT */ if ( (word_column_number == window_columns/WORD_COLUMN_LENGTH - 1 ) /* is word_column = last_word_column? */ && (*dptr > WORD_COLUMN_LENGTH) ) /* and length of current word longer than screen length left */ { memcpy((outbuf+offset),dptr+1,WORD_COLUMN_LENGTH-1); /* insert word into OUTSTR */ *(outbuf+offset+WORD_COLUMN_LENGTH-1) = '.'; /* dot indicating word goes off edge of screen */ } else memcpy((outbuf+offset),dptr+1,*dptr); /* insert word into OUTSTR */ dptr += *dptr+1; /* move to next word */ }/*endif slot empty*/ }/*endif first column*/ }/* END LOOP DOWN ALL WINDOW ROWS in this word_column */ ++word_column_number; } while( word_column_number < window_columns/WORD_COLUMN_LENGTH ); /* END LOOP ACROSS ALL WINDOW WORD COLUMNS */ /* We drop out here when our OUTSTR buffer is full */ dicptrz = dptr; /* set DICPTRZ */ sig_array[0] = SS$_NORMAL; /* (next line) if (window_rows*window_columns) = 0 then use MAX_WORD_SIZE (for use by DUMP_DICTIONARY) */ sig_array[1] = ( (window_rows*window_columns) == 0 ? MAX_WORD_SIZE : (window_rows*window_columns) ); sig_array[2] = outbuf; fmtoutstr( 3, &sig_array ); free(outbuf); /* release temporary memory */ } /*-------------------------------------------------------------------------- .SUBTITLE SPELL_GUESS Functional Description: Guesses the spelling of misspelled word stored in DIC_LWA,DIC_LWL. Algorythm taken from the very popular Vassar Spelling Checker. With credit to Vassar where credit is due. Calling Sequence: spell_guess(); Argument inputs: DIC_LWA - Address of misspelled word DIC_LWL - Length of misspelled word GMODE - guess mode (1=reversals,2=vowels,3=minus,4=plus,5=consonants,6=giveup) GOF - guess column offset (character # in word working on) GSUBMODE- (char) guess submode (letter we're currently replacing with) Outputs: (retcode is placed in outstr by fmtoutstr) retcode=LIB$_NORMAL, outline="guessed word" here's another word to try, ask user if guessed word is what he ment. or retcode = SS$_ENDFILE, no more guesses. Outline: 1. Reversals (test for transposed characters) 2. vowels (test for wrong vowel used) 3. minus chars (test for extra character in word) 4. plus chars (test for character missing from word) 5. consonants (test for wrong character used) 6. give up (give up) ---------------------------------------------------------------------------*/ #define GUSREV 1 /* 1 = GUESS REVERSALS */ #define GUSVOL 2 /* 2 = GUESS VOWELS */ #define GUSMIN 3 /* 3 = GUESS MINUS */ #define GUSPLS 4 /* 4 = GUESS PLUS */ #define GUSCON 5 /* 5 = GUESS CONSONANTS */ #define GIVEUP 6 /* 6 = GIVE UP */ void spell_guess() { switch (gmode) /* GUESS MODE */ { case GUSREV: /* 1 = GUESS REVERSALS */ if (spell_gusrev()) return; /* TRUE if guess word found. outstr set. gcol, gmode, gsubmode hold our place for reentry */ ++gmode; /* go to next guess mode */ gof = gsubmode = 0; /* reset GOF and GSUBMODE */ /* DROP THROUGH TO NEXT GUESS MODE */ case GUSVOL: /* 2 = GUESS VOWELS */ if (spell_gusvol()) return; /* TRUE if guess word found. outstr set. gcol, gmode, gsubmode hold our place for reentry */ ++gmode; /* go to next guess mode */ gof = gsubmode = 0; /* reset GOF and GSUBMODE */ /* DROP THROUGH TO NEXT MODE: GUSMIN */ case GUSMIN: /* 3 = GUESS MINUS */ if (spell_gusmin()) return; /* TRUE if guess word found. outstr set. gcol, gmode, gsubmode hold our place for reentry */ ++gmode; /* go to next guess mode */ gof = gsubmode = 0; /* reset GOF and GSUBMODE */ /* DROP THROUGH TO NEXT MODE: GUSPLS */ case GUSPLS: /* 4 = GUESS PLUS */ if (spell_guspls()) return; /* TRUE if guess word found. outstr set. gcol, gmode, gsubmode hold our place for reentry */ ++gmode; /* go to next guess mode */ gsubmode = 0; /* reset GSUBMODE */ gof = 0; /* reset GOF */ /* DROP THROUGH TO NEXT MODE: GUSCON */ case GUSCON: /* 5 = GUESS CONSONANTS */ if (spell_guscon()) return; /* TRUE if guess word found. outstr set. gcol, gmode, gsubmode hold our place for reentry */ /* DROP THROUGH TO NEXT MODE: GIVEUP */ case GIVEUP: /* 6 = GIVE UP */ gmode = GUSREV; /* reset */ gof = gsubmode = 0; /* reset GOF and GSUBMODE */ fmtoutstr(1, &SS$_ENDOFFILE ); /* no more guesses */ } } /*--------------------------------------------------------------------------*/ int spell_gusrev() { int status; int sig_array[3]; char guess_word[BUFLEN]; char temp; /* Guess reversals. Copy word and transpose x with x+1 */ while(gof < dic_lwl-1) /* test for beyond end of word */ { if (dic_lwa[gof] != dic_lwa[gof+1]) /* don't swap if characters are identical */ { memcpy(guess_word,dic_lwa,dic_lwl); /* copy over word */ temp = guess_word[gof]; /* swap chars */ guess_word[gof] = guess_word[gof+1]; guess_word[gof+1] = temp; status = dic_lookup_word( dic_lwl, guess_word ); /* see if word exists */ if (status & STS$M_SUCCESS) { ++gof; /* move to next character for reentry */ sig_array[0] = status; /* retcode */ sig_array[1] = dic_lwl; /* length of output string */ sig_array[2] = guess_word; /* address of output string */ fmtoutstr( 3, &sig_array ); /* format output string */ return(SUCCESS); /* return with string containing a correctly spelled word, status */ } } ++gof; /* move to next character */ } return(FAILURE); /* no more guess words found */ } int spell_gusvol() { int status; int sig_array[3]; char guess_word[BUFLEN]; /* Guess vowel replacements. For each {a,e,i,o,u} replace with {a,e,i,o,u} GSUBMODE goes from 0-4 as letter replacement goes a,e,i,o,u */ while(gof < dic_lwl) /* test for beyond end of word */ { memcpy(guess_word,dic_lwa,dic_lwl); /* copy over word */ if (guess_word[gof] == 'a' || guess_word[gof] == 'e' || guess_word[gof] == 'i' || guess_word[gof] == 'o' || guess_word[gof] == 'u') { while(gsubmode < 5) { switch (gsubmode) { case 0: guess_word[gof] = 'a'; break; /* 1 = replace with an "a" */ case 1: guess_word[gof] = 'e'; break; /* 2 = replace with an "e" */ case 2: guess_word[gof] = 'i'; break; /* 3 = replace with an "i" */ case 3: guess_word[gof] = 'o'; break; /* 4 = replace with an "o" */ case 4: guess_word[gof] = 'u'; break; /* 5 = replace with an "u" */ default: return(EDX__GUSINTERR2); /* return but don't signal (gsubmode may be 6 is OK) */ } if (guess_word[gof] != dic_lwa[gof]) /* if we didn't replace vowel with same vowel */ { status = dic_lookup_word( dic_lwl, guess_word ); /* see if word exists */ if (status & STS$M_SUCCESS) { ++gsubmode; /* set to guess next vowel for next time */ sig_array[0] = status; /* retcode */ sig_array[1] = dic_lwl; /* length of output string */ sig_array[2] = guess_word; /* address of output string */ fmtoutstr( 3, &sig_array ); /* format output string */ return(SUCCESS); /* return with string containing a correctly spelled word, status */ }/*endif(status);*/ }/*endif(guess_word[gof]!=dic_lwa[gof]);*/ ++gsubmode; /* move to next vowel */ }/*endwhile(gsubmode<5)*/ gsubmode=0; /* reset gsubmode */ }/*endif(guessword=aeiou*/ ++gof; /* move to next character */ }/*endwhile(gof<dic_lwl-1)*/ return(FAILURE); /* no more guesses */ } int spell_gusmin() { int status; int sig_array[3]; char guess_word[BUFLEN]; /* Guess minus. Test for extra character. Try eliding one character at a time */ while(gof < dic_lwl) /* test for beyond end of word */ { if (gof == 0 || dic_lwa[gof] != dic_lwa[gof-1]) /* skip if prev char = current char. The result would be the same */ { /* as last time. (Also check gof==0 first) */ memcpy(&guess_word[0],&dic_lwa[0],gof); /* copy over word */ memcpy(&guess_word[gof],&dic_lwa[gof+1],dic_lwl-(gof+1));/* shift GOF'th+1 to end of word left one */ status = dic_lookup_word( dic_lwl-1, guess_word ); /* see if word exists */ if (status & STS$M_SUCCESS) { ++gof; /* move to next char for reentry */ sig_array[0] = status; /* retcode */ sig_array[1] = dic_lwl-1; /* length of output string */ sig_array[2] = guess_word; /* address of output string */ fmtoutstr( 3, &sig_array ); /* format output string */ return(SUCCESS); /* return with string containing a correctly spelled word, status */ }/*endif(status);*/ }/*endif(not double char)*/ ++gof; /* move to next char */ }/*endwhile(gof<dic_lwl)*/ return(FAILURE); /* no more guesses */ } int spell_guspls() { int status; int sig_array[3]; char guess_word[BUFLEN]; char guess_char; /* Guess plus. Test if a letter is missing from word. Add one letter anywhere in word. */ /* GSUBMODE goes from 0-25 as letter replacement goes from a-z */ while(gof <= dic_lwl) /* test for beyond end of word */ { memcpy(&guess_word[0],&dic_lwa[0],gof); /* copy over word */ memcpy(&guess_word[gof+1],&dic_lwa[gof],dic_lwl-gof); /* shift GOF'th+1 to end of word left one */ while(gsubmode < 26) /* test for GSUBMODE=25 (all letters of alphabet) */ { guess_char = gsubmode + 'a'; /* convert GSUBMODE={0-25} to ASCII {A-Z} (which is {65-90} */ if (gof == 0 || guess_char != dic_lwa[gof-1]) /* if extra char being inserted = char it's infront of */ { /* then don't do it to avoid duplicates */ guess_word[gof] = guess_char; /* insert missing letter */ status = dic_lookup_word( dic_lwl+1, guess_word ); /* see if word exists */ if (status & STS$M_SUCCESS) { ++gsubmode; /* set to try next char on reentry */ sig_array[0] = status; /* retcode */ sig_array[1] = dic_lwl+1; /* length of output string */ sig_array[2] = guess_word; /* address of output string */ fmtoutstr( 3, &sig_array ); /* format output string */ return(SUCCESS); /* return with string containing a correctly spelled word, status */ }/*endif(status);*/ }/*endif(not double char)*/ ++gsubmode; /* try next char */ }/*endwhile(gsubmode<26)*/ gsubmode=0; /* reset gsubmode */ ++gof; /* move to next char */ }/*endwhile(gof<dic_lwl)*/ return(FAILURE); /* no more guesses */ } int spell_guscon() { int status; int sig_array[3]; int isvowel; char guess_word[BUFLEN]; char guess_char; /* Guess consonants. Test for any one character wrong. Replace each character with every other character of the alphabet GSUBMODE goes from 0-25 as letter replacement goes from a-z */ while(gof < dic_lwl) /* test for beyond end of word */ { if ( (dic_lwa[gof] == 'a') /* if char is a vowel */ || (dic_lwa[gof] == 'e') || (dic_lwa[gof] == 'i') || (dic_lwa[gof] == 'o') || (dic_lwa[gof] == 'u') ) isvowel = TRUE; else isvowel = FALSE; while(gsubmode < 26) /* test for GSUBMODE=25 (all letters of alphabet) */ { guess_char = gsubmode + 'a'; /* convert GSUBMODE={0-25} to ASCII {A-Z} (which is {65-90} */ if ( (guess_char != dic_lwa[gof]) /* if overstrike char != original char */ && ( !isvowel ) /* and char being replaced isn't a vowel */ || ( (guess_char != 'a') /* or if char being replaced is a vowel */ && (guess_char != 'e') /* then do only if guess_char isn't a vowel */ && (guess_char != 'i') /* (otherwise we already tried it in spell_gusvol) */ && (guess_char != 'o') && (guess_char != 'u') ) ) { /* or then don't do it to avoid duplicates */ memcpy(guess_word,dic_lwa,dic_lwl); /* copy over word */ guess_word[gof] = guess_char; /* overstrike with another letter */ status = dic_lookup_word( dic_lwl, guess_word ); /* see if word exists */ if (status & STS$M_SUCCESS) { ++gsubmode; /* set to try next char on reentry */ sig_array[0] = status; /* retcode */ sig_array[1] = dic_lwl; /* length of output string */ sig_array[2] = guess_word; /* address of output string */ fmtoutstr( 3, &sig_array ); /* format output string */ return(SUCCESS); /* return with string containing a correctly spelled word, status */ }/*endif(status);*/ }/*endif(not double char)*/ ++gsubmode; /* try next char */ }/*endwhile(gsubmode<26)*/ gsubmode=0; /* reset gsubmode */ ++gof; /* move to next char */ }/*endwhile(gof<dic_lwl)*/ return(FAILURE); /* no more guesses */ } /*--------------------------------------------------------------------------- .SUBTITLE ACCEPTED_WORD_LIST SPELL_ACCEPT_WORD Functional Description: Constructs a balanced binary tree of words which the user has instructed us to accept as properly spelled. The VMS library routine LIB$INSERT_TREE is used to build the tree. DIC_LOOKUP_WORD uses the VMS library routine LIB$LOOKUP_TREE to search this tree for a match before declaring a word misspelled. The routines ALLOCATE_NODE and COMPARE_NODE are called by LIB$INSERT_TREE and LIB$LOOKUP_TREE. ALLOCATE_NODE allocates memory and inserts the word to accept in the allocated memory. COMPARE_NODE alphabetically compares the word stored in a given memory block with a given word to determine which comes first. Calling Sequence: spell_accept_word(); Inputs: dic_lwa - (Global) Pointer to word to accept. Set by DIC_LOOKUP_WORD. dic_lwl - (Global) Length of word to accept. Set by DIC_LOOKUP_WORD. */ void spell_accept_word() { int status; struct dsc$descriptor_s word_desc = { 0, DSC$K_DTYPE_T, DSC$K_CLASS_S, 0 }; word_desc.dsc$w_length = dic_lwl; word_desc.dsc$a_pointer = dic_lwa; status = lib$insert_tree( &accept_tree, &word_desc, &0, compare_node, allocate_node, &newnode, 0); fmtoutstr( 1, &status ); } /*=========================================================================== SAVE_CORRECTION Functional Description: Constructs a balanced binary tree of misspelled words and their corrections. String #1 is the misspelled word and string #2 is the correct spelling. The VMS library routine LIB$INSERT_TREE is used to build the tree. After declaring a word misspelled, SPELL_TEXTLINE uses the VMS library routine LIB$LOOKUP_TREE to search this tree for a match. If a match is found the correct spelling is returned to EDX. EDX then asks the user if he wishes to make the correction. The routines ALLOCATE_NODE and COMPARE_NODE are called by LIB$INSERT_TREE and LIB$LOOKUP_TREE. ALLOCATE_NODE allocates memory and inserts the word to accept in the allocated memory. COMPARE_NODE alphabetically compares the word stored in a given memory block with a given word to determine which comes first. Calling Sequence: save_correction( inwds_desc ); Inputs: inwds_desc - Descriptor of string containing misspelled word and correct word. The format of the string is misspelled word followed by a single space followed by the correct word. Outline: 1. descriptors str1_desc & str1_desc are initialized. 2. inwds string is parsed: first word is placed in str1 second word is placed in str2 Leading whitespace is skipped and characters are lowercased in the process. The string lengths are placed in the .dsc$w_length part of each string descriptor. 3. LIB$INSERT_TREE is called to add a new node to the tree. ---------------------------------------------------------------------------*/ void save_correction( struct dsc$descriptor_s *inwds_desc ) { int i; int status; char *inend; /* points to one char after end of input string */ char *wdptr; /* points into string */ char str1[BUFLEN]; char str2[BUFLEN]; struct dsc$descriptor_s str1_desc = { 0, DSC$K_DTYPE_T, DSC$K_CLASS_S, str1 }; struct dsc$descriptor_s str2_desc = { 0, DSC$K_DTYPE_T, DSC$K_CLASS_S, str2 }; /* SKIP OVER LEADING SPACES AND TABS UNTIL WE FIND BEGINNING OF WORD OR END OF STRING. ERROR IF STRING IS BLANK. */ inend = inwds_desc->dsc$a_pointer + inwds_desc->dsc$w_length; /* inend -> char after last char of instring */ for ( wdptr = inwds_desc->dsc$a_pointer; (wdptr < inend) && isspace(*wdptr); /* Check for out of bounds before checking character */ ++wdptr ); if (wdptr == inend) /* was blank line */ { fmtoutstr( 1, &FAILURE ); return; } /* MOVE 1ST WORD (MISSPELLED WORD) TO STR1 AND LOWERCASE */ for ( i = 0; wdptr < inend && i < BUFLEN && !isspace(*wdptr); ++wdptr, ++i ) str1[i] = _tolower(*wdptr); if (wdptr == inend || i == BUFLEN) /* error if end of string (no second word) */ { /* or word1 overflowed buffer (too long) */ fmtoutstr( 1, &FAILURE ); return; } str1_desc.dsc$w_length = i; /* length of 1st word */ /* MOVE 2ND WORD (CORRECTION) TO STR2 AND LOWERCASE */ for ( ; /* SKIP OVER WHITESPACE. Look for beginning of second word */ (wdptr < inend) && isspace(*wdptr); /* (Check for out of bounds before checking character) */ ++wdptr ); for ( i = 0; /* NOW MOVE WORD TO STR2 */ wdptr < inend /* (Check for out of bounds before checking character) */ && i < BUFLEN && isascii(*wdptr); ++wdptr, ++i ) str2[i] = _tolower(*wdptr); if (i == BUFLEN) /* error if word1 overflowed buffer (too long) */ { /* (Reaching end of input string is OK) */ fmtoutstr( 1, &FAILURE ); return; } str2_desc.dsc$w_length = i; /* length of 2nd word */ status = lib$insert_tree( &savcor_tree, &str1_desc, &0, compare_node, allocate_node, &newnode, &str2_desc); fmtoutstr( 1, &status ); } /*---------------------------------------------------------------------------*/ /* The following routines are FOR DEBUGGING PURPOSES ONLY */ #if DEBUG void print_accept_node(char *node) { char *ptr, *end; ptr = (char *)(node+12); end = ptr + *(unsigned short *)(node+10); for ( ; ptr < end; ++ptr) printf("%c",*ptr); printf("\n"); } void print_savcor_node(char *node) { char *ptr, *end; ptr = (char *)(node+12); end = ptr + *(unsigned short *)(node+10); for ( ; ptr < end; ++ptr) /* print the misspelled word */ printf("%c",*ptr); printf(" "); end = ptr + 1 + *(unsigned char *)(ptr); for ( ; ptr < end; ++ptr) /* print the correct spelling */ printf("%c",*ptr); printf("\n"); } void traverse_savcor_tree() { lib$traverse_tree( &savcor_tree, print_savcor_node ); } void traverse_accept_tree() { lib$traverse_tree( &accept_tree, print_accept_node ); } #endif /*=========================================================================== ALLOCATE_NODE Functional Description: Allocates memory for a new node being added in a balanced binary tree by LIB$INSERT_TREE. (alloc-rtn) Calling Sequence: Called by LIB$INSERT_TREE psudo-c call: status = allocate_node( symstr_desc, retadr, usrdat_desc ); Argument inputs: number of arguments = 3 symstr_desc (input) - Pointer to string descriptor of string to insert in this node. (Descriptor class and type fields not used). retadr (output) - Address to place starting address of block of memory allocated and filled in. usrdat_desc [user data] - If non-zero, address of string descriptor of string #2. Outputs: return status = LIB$_NORMAL if successfull return status = LIB$_INSVIRMEM if unsuccessfull Outline: 1. Memory is allocated and filled in as shown below: Map of node being allocated: -----------------------------------------( address of node placed in ret-adr ) | left link | ----------------------------------------- | right link | ----------------------------------------- | STRING#1 LENGTH | balance | ----------------------------------------- | STRING#1 | 12 | . | | . | ----------------------------------------- | String #2 |length #2| (optional string#2) | . +---------| | . | ----------------------------------------- The node header, consisting of the first 10 bytes of the node containing the left link, right link, and balance, is reserved for use by LIB$INSERT_TREE We fill in the string length, and the string itself, and we allocate enough memory to hold it all (12 bytes + length of string, +1 byte + length of string#2 if present). If this is the tree of saved corrections, then the first string is a misspelled word we came across earlier and string #2 is the correction we made last time. Upon finding this same misspelled word a second time, EDX will ask if the user wants to make the same correction. ---------------------------------------------------------------------------*/ int allocate_node( struct dsc$descriptor_s *symstr_desc, char **retadr, struct dsc$descriptor_s *usrdat_desc ) { int memlen; /* length of memory to allocate */ memlen = 12 + symstr_desc->dsc$w_length; /* calculate length of memory = 12 for header + length of string (from descriptor) [(+1+length of string#2 if present)] */ if (usrdat_desc != 0) /* test for string#2 */ memlen += 1 + usrdat_desc->dsc$w_length; /* [(+1+length of string#2 if present)] */ *retadr = malloc( memlen ); if (*retadr == 0) { edx_signal(1, byref(EDX__MEMERR) ); fmtoutstr( 1, byref(EDX__MEMERR) ); return(LIB$_INSVIRMEM); } *(unsigned short *)(*retadr+10) = symstr_desc->dsc$w_length; /* fill in length of string */ memcpy(*retadr+12,symstr_desc->dsc$a_pointer,symstr_desc->dsc$w_length); /* fill in string */ if (usrdat_desc != 0) /* test for string#2 */ { *(char *)(*retadr + 12 + symstr_desc->dsc$w_length) = usrdat_desc->dsc$w_length; /* fill in length of string #2 */ memcpy(*retadr + 12 + symstr_desc->dsc$w_length + 1, usrdat_desc->dsc$a_pointer, usrdat_desc->dsc$w_length); /* fill in string #2 */ } return(LIB$_NORMAL); } /*=========================================================================== COMPARE_NODE Functional Description: Compares string to string contained in a given node. Returns >0, =0, <0 for string GTR,EQL,LSS than given node. Calling Sequence: Called by LIB$INSERT_TREE: psudo-c call: status = compare_node(symstr_desc, treenode, usrdat_desc ); Argument inputs: number of arguments = 3 symstr_desc (input) - Address of string descriptor of string to compare with given node. (Descriptor class and type fields not used). treenode (input) - Address of node to compare with string. The format of a node is shown in the memory map below. usrdat_desc [user-data] (not used) - The address of a descriptor, or 0. Outputs: status = >0 if string > node =0 if string = node <0 if string < node Map of tree node: ----------------------------------------- | left link | 00 ----------------------------------------- | right link | 04 ----------------------------------------- | STRING LENGTH | balance | 08 ----------------------------------------- | STRING | 0C (=decimal 12) | . | | . | ----------------------------------------- ---------------------------------------------------------------------------*/ int compare_node( struct dsc$descriptor_s *symstr_desc, char *treenode, struct dsc$descriptor_s *usrdat_desc ) { return string_compare( symstr_desc->dsc$w_length, /* length of symstr */ symstr_desc->dsc$a_pointer, /* address of symstr */ *(unsigned short *)(treenode+10), /* length of node string */ (char *)(treenode+12) ); /* address of node string */ } /*=========================================================================== .SUBTITLE ADD_PERSDIC ADD_PERSDIC Functional Description: Adds the current unrecognised word to the user's personal dictionary. Calling Sequence: spell_accept_word(); Global Inputs: DIC_LWA - Address of word to accept. Set by DIC_LOOKUP_WORD. DIC_LWL - Length of word to accept. Set by DIC_LOOKUP_WORD. Outline: 1. Open user's personal dictionary. If file does not exist, it is created. 2. Add new word to end of file. 3. Close file. ---------------------------------------------------------------------------*/ void add_persdic() { /* HOW TO INITIALIZE THIS ONLY ONCE? */ int status; int sig_array[6]; struct FAB addpdicfab; struct RAB addpdicrab; struct NAM addpdicnam; /* OPEN THE USER'S PERSONAL DICTIONARY FILE */ /* Initialize ADDPDICNAM */ addpdicnam = cc$rms_nam; /* initialize NAM */ addpdicnam.nam$b_rss = NAM$C_MAXRSS; /* Expanded file name string size */ /* Initialize ADDPDICFAB */ addpdicfab = cc$rms_fab; /* initialize FAB */ addpdicfab.fab$l_fna = (char *) "EDXPERSDIC"; /* file name address */ addpdicfab.fab$b_fns = LENGTH("EDXPERSDIC"); /* file name length */ addpdicfab.fab$l_dna = (char *) "SYS$LOGIN:EDXPERSDIC.DAT"; /* default file name address */ addpdicfab.fab$b_dns = LENGTH("SYS$LOGIN:EDXPERSDIC.DAT"); /* default file name length */ addpdicfab.fab$l_nam = &addpdicnam; /* NAM block address */ addpdicfab.fab$b_fac = FAB$M_PUT; /* File Access options */ addpdicfab.fab$b_shr = FAB$M_SHRGET; /* share options */ addpdicfab.fab$l_fop = FAB$M_CIF; /* options = create if nonexist */ /* Initialize ADDPDICRAB */ addpdicrab = cc$rms_rab; /* initialize RAB */ addpdicrab.rab$l_fab = &addpdicfab; /* address of FAB */ addpdicrab.rab$l_rop = RAB$M_EOF; /* Position to end of file for append operation */ status = sys$create(&addpdicfab); /* Open user's personal dictionary file */ if ( !(status & STS$M_SUCCESS) ) { sig_array[0] = EDX__PERSDICERR; /* error opening personal dictionary */ sig_array[1] = 2; /* 2 FAO args */ sig_array[2] = addpdicnam.nam$b_rsl; /* filename size */ sig_array[3] = addpdicnam.nam$l_rsa; /* filename address */ sig_array[4] = status; /* Error status */ sig_array[5] = 0; /* 0 FAO args */ edx_signal( 6, sig_array ); } if ( (status == RMS$_CREATED) ) { sig_array[0] = EDX__CREPERSDIC; /* created personal dictionary */ sig_array[1] = 2; /* 2 FAO args */ sig_array[2] = addpdicnam.nam$b_rsl; /* filename size */ sig_array[3] = addpdicnam.nam$l_rsa; /* filename address */ edx_signal( 4, sig_array ); } status = sys$connect(&addpdicrab); /* Connect to user's personal dictionary file */ if ( !(status & STS$M_SUCCESS) ) { sig_array[0] = EDX__PERSDICERR; /* error opening personal dictionary */ sig_array[1] = 2; /* 2 FAO args */ sig_array[2] = addpdicnam.nam$b_rsl; /* filename size */ sig_array[3] = addpdicnam.nam$l_rsa; /* filename address */ sig_array[4] = status; /* Error status */ sig_array[5] = 0; /* 0 FAO args */ edx_signal( 6, sig_array ); } addpdicrab.rab$l_rbf = dic_lwa; addpdicrab.rab$w_rsz = dic_lwl; status = sys$put(&addpdicrab); /* Add word to user's personal dictionary */ if (status & STS$M_SUCCESS) { sig_array[0] = EDX__WORDADD; /* added word to personal dictionary file */ sig_array[1] = 4; /* 2 FAO args */ sig_array[2] = dic_lwl; /* word size */ sig_array[3] = dic_lwa; /* word address */ sig_array[4] = addpdicnam.nam$b_rsl; /* filename size */ sig_array[5] = addpdicnam.nam$l_rsa; /* filename address */ edx_signal( 6, sig_array ); } else edx_signal( addpdicrab.rab$l_sts, addpdicrab.rab$l_stv ); /* signal error */ sys$close(&addpdicfab); /* close file */ } /*---------------------------------------------------------------------------- .SUBTITLE DUMP_COMMONWORDS DUMP_COMMONWORDS Functional Description: Returns as a single string the list of common words as stored in the EDX dictionary database file. Calling Sequence: dump_commonwords(); Outputs: OUTSTR - String of common words, blank separated. Outline: 1. Scratch memory is allocated. 2. The string of common words is copied to the scratch memory. 3. The length-bytes preceeding each word are replaced with space characters. 4. The resulting string consisting of the common words, blank separated, is sent to fmtoutstr(). 5. The scratch memory is deallocated. */ void dump_commonwords() { int wlen; /* word length */ char *outbuf; /* output buffer */ char *lbptr; /* pointer to length-byte of current word */ char *endrange; int sig_array[3]; outbuf = (char *) malloc(dichead.cwdlen); /* get a temporary buffer */ memcpy(outbuf,cmnwdsptr,dichead.cwdlen); /* copy common words to buffer */ endrange = outbuf + dichead.cwdlen; /* end of commonwords */ lbptr = outbuf; /* start at beginning of common words */ while (lbptr < endrange) /* until we reach the end of the common word list */ { wlen = *lbptr; /* length of this word */ *lbptr = ' '; /* change length-byte to space */ lbptr += wlen + 1; /* move to next word */ } sig_array[0] = SUCCESS; /* return status */ sig_array[1] = dichead.cwdlen; /* length */ sig_array[2] = outbuf; /* address */ fmtoutstr( 3, &sig_array ); free(outbuf); /* free memory */ } /* ============================================================================== EDX SORT ============================================================================== Sort Routines: EDX_SORT !Main entry. Parses R6 SORT_PREPARSE !Preparse SORT command SORT_PASSFILES !Pass filenames for file sort SORT_POSTPARSE !Finish parsing SORT command SORT_DO_FILE !Do file sort SORT_RELEASE_REC !Give record to sort when using record sort SORT_RETURN_REC !Get record when using record sort .SUBTITLE EDX_SORT Functional Description: Sorts using either file sort or record sort. This routine uses the VMS Sort/Merge (SOR) Utility Routines as described in the VAX/VMS Utilities Routines Reference Manual. Calling Sequence: edx_sort(); Argument inputs: incodel = Code describing subfunction to perform (low word of INCODE) 1. Preparse command line 2. Pass files and do sort (for file sort) 3. Postparse command line 4. Pass a record to sort. (Repeat until all records passed) 5. Do record sort 6. Receive a record in sorted order. (Repeat until all records received) 7. Cleanup Usage: Either file sort or record sort is used. With file sort, you pass it the name of a file on disk to sort, it sorts the file creating a new file and returns to you the name of the new file created. With record sort, you pass it individual records (a record is a line of text from the buffer), passing it one record per call until all records have been passed. The records are sorted, and then returned to you one record per call, in sorted order, until all records have been returned to you. Which sorting method to use: File sort can handle all situations. It is the method of choice when a large number of records are to be sorted, since it is faster to write a large buffer to disk than to pass it one line at a time to and from sort, and there is no limit to the length of line which can be sorted. However, the file sort method takes a minimum of a couple seconds because of time it takes to create a temporary file on disk and then delete it. Record sort is suitable when a small number of lines are to be quickly sorted, and the lines are < 132 characters in length (SRT_MAXLRL). As the number of records to sort increases, there reaches a point where it becomes faster to use the file sort method instead. Sequence of calls for performing file sort: 1. Call n=1 'Preparse command line' passing the SORT command line to be parsed. Returns indicating if SORT BUFFER, SORT RANGE, HELP, or error in sort command. 2. Call n=2 'Pass files and do sort' passing it name of file to sort. Returns name of sorted file created. Sequence of calls for performing record sort: 1. Call n=1 'Preparse command line' passing the SORT command command line to be parsed. Returns indicating if SORT BUFFER, SORT RANGE, HELP, or error in sort command. 2. Call n=3 'Postparse command line' 3. Call n=4 'Pass a record to sort' passing it one record from the buffer. Repeat this call until all records have been passed. 4. Call n=5 'Do record sort' to perform the actual sort 5. Call n=6 'Receive a record in sorted order'. Returns one record. Repeat until all records have been passed back in sorted order. 6. Call n=7 'Cleanup' to free up memory allocated by sort. ---------------------------------------------------------------------- Internal sequence for performing file sort: 1. EDX calls SORT_PREPARSE passing it the SORT command line. SORT_PREPARSE parses the command line. If there is an error it returns with error status. Otherwise it returns indicating wheter SORT BUFFER or SORT RANGE was specified on command line. 2. EDX writes a temporary file to disk to be sorted. 3. EDX calls SORT_PASSFILES passing it the name of the temporary file to be sorted. SORT_PASSFILES generates a filename for the output file, and then calls SOR$PASS_FILES passing the names of the input file and the output file. 4. SORT_POSTPARSE is called. It extracts the information from CLI about the previously parsed SORT command, and calls SOR$BEGIN_SORT. 5. SORT_DO_FILE is called, which calls SOR$SORT_MERGE to do the actual sort. It also calls SOR$END_SORT to clean up afterwards. Internal sequence for performing record sort: 1. EDX calls SORT_PREPARSE passing it the SORT command line. SORT_PREPARSE parses the command line. If there is an error it returns with error status. Otherwise it sets OUTSTR indicating wheter SORT BUFFER or SORT RANGE was specified on command line. 2. SORT_POSTPARSE is called. It extracts the information from CLI about the previously parsed SORT command, and calls SOR$BEGIN_SORT. Return to user with OUTSTR previously set. 3. SOR$RELEASE_REC is called for n=4. 4. SOR$SORT_MERGE is called for n=5. 5. SOR$RETURN_REC is called for n=6. 6. SOR$END_SORT is called for n=7. Note: The SOR$M_... symbols are defined in the SYS$LIBRARY:SORTSHR.EXE shareable image. The linker should resolve these symbols automatically, as part of its search through the images in the system shareable image library SYS$LIBRARY:IMAGELIB.OLB. ------------------------------------------------------------------------------*/ #define SRT_MAXLRL 132 /* Maximum length of line we will support for record sort */ /* There is a tradeoff here between memory used and speed achieved */ /* A larger MAXLRL causes SORT to allocate more memory */ #define ASCENDING 0 /* Used in key_buffer */ #define DESCENDING 1 /* Used in key_buffer */ #define FILE_SORT 0 /* sort_type = FILE_SORT */ #define RECORD_SORT 1 /* sort_type = RECORD_SORT */ void edx_sort() { int incodel; int status; switch (*incode_ptr) { case SORT_FILE_INIT: sort_preparse(); /* Sort initialize for file sort */ return; case SORT_FILE_DO: status = sort_passfiles(); /* Do file sort */ if (status & STS$M_SUCCESS) /* Success passing filenames */ { status = sort_postparse(FILE_SORT); /* using file sort */ if (status & STS$M_SUCCESS) /* Success sorting */ { sort_do_file(); return; /* Normal return */ } } fmtoutstr( 1, &status ); /* else return bad status */ return; case SORT_RECORDS_INIT: status = sort_postparse(RECORD_SORT); /* Sort initialize for record sort */ fmtoutstr( 1, &status ); return; case SORT_RECORDS_PASS: sort_release_rec(); /* Pass a record to sort */ return; case SORT_RECORDS_DO: status = sor$sort_merge(); /* Do record sort */ if ( !(status & STS$M_SUCCESS) ) /* Error sorting */ edx_signal(1,&status); fmtoutstr( 1, &status ); return; case SORT_RECORDS_RECV: sort_return_rec(); /* Receive a record in sorted order */ return; case SORT_FINISH: status = sor$end_sort(); /* Cleanup record sort */ fmtoutstr( 1, &status ); return; default: edx_signal(1, byref(EDX__UNKNCODE) ); return; } } /*--------------------------------------------------------------------------- SORT_PREPARSE Functional Description: Parses the command line for correctness and returns indicating if BUFFER or RANGE was specified. We hold off on examining the rest of the results from the parsed command line until after SOR$PASS_FILES has been called (if file sort is being used), then the rest is done in SORT_POSTPARSE. Calling Sequence: sort_preparse(); Argument inputs: INSTR = Command line to be parsed Outputs: OUTSTR - return value indicating domain to be sorted. = 0 no verb specified = 1 BUFFER was specified = 2 RANGE was specified = 3 HELP was specified ---------------------------------------------------------------------------*/ void sort_preparse() { int status; $CONST_DESCRIP(domain_desc,"DOMAIN"); char valbuf[8]; $DESCRIPTOR( valbuf_desc, valbuf ); /* Clean up any outstanding sort */ sor$end_sort(); /* clean up incase previous unfinished sort was active */ /* The command is parsed. */ status = cli$dcl_parse(&instr_desc, &edx_commands); /* Parse input string */ if ( !(status & STS$M_SUCCESS) ) /* Error parsing input string */ { fmtoutstr( 1, &FAILURE ); return; } /* Now see if BUFFER, RANGE, or HELP was specified */ cli$get_value( &domain_desc, &valbuf_desc, &valbuf_desc.dsc$w_length ); if ( string_compare( valbuf_desc.dsc$w_length, valbuf_desc.dsc$a_pointer, valbuf_desc.dsc$w_length, "BUFFER" ) == 0 ) /* User may specify B, BU, BUF, BUFF, BUFFE, BUFFER */ fmtoutstr( 1, &3 ); /* BUFFER specified */ else if ( string_compare( valbuf_desc.dsc$w_length, valbuf_desc.dsc$a_pointer, valbuf_desc.dsc$w_length, "RANGE" ) == 0 ) /* User may specify R, RA, RAN, RANG, RANGE */ fmtoutstr( 1, &2 ); /* RANGE specified */ else if ( string_compare( valbuf_desc.dsc$w_length, valbuf_desc.dsc$a_pointer, valbuf_desc.dsc$w_length, "HELP" ) == 0 ) fmtoutstr( 1, &4 ); /* HELP specified */ else fmtoutstr( 1, &0 ); /* no verb specified */ } /*--------------------------------------------------------------------------- SORT_PASSFILES Functional Description: Calls SOR$PASS_FILES, passing the name of the input filename to be sorted, and the output filename to create. The input filename of the file to sort is passed to us in INSTR. The output filename is generated by us here. It is of the form "EDX_TEMPSORT00000000.SRT" where the 00000000 is the current process's PID in hexadecimal. We use the current process's PID as part of the filename to help make the filename unique. Calling Sequence: status = sort_passfiles(); Argument inputs: INSTR = Input filename to pass to SOR$PASS_FILES Outputs: OUTSTR = Output filename we generated, with return status of 1 Outline: The output filename is of the form: EDX_TEMPSORT00000000.SRT where the 00000000 is replaced by the process's PID number. A. The process's PID is determined by calling SYS$GETJPI B. 00000000 of the output filename is replaced by the process's PID number. (OTS$CVT_L_TZ) C. SOR$PASS_FILES is called, passing the input filename from INSTR, and the output filename we created. D. If success, then our generated output filename is copied to OUTSTR (for temporary storage). */ int sort_passfiles() { int status; int pid; int sig_array[3]; char outfile[] = { "EDX_TEMPSORT00000000.SRT" }; $DESCRIPTOR(outfile_desc,outfile); struct dsc$descriptor_s pid_desc = { 8, DSC$K_DTYPE_T, DSC$K_CLASS_S, &outfile[12] }; item_list_3 itemlist[2] = { { 4, JPI$_PID, &pid, 0 }, { 0, 0, 0, 0 } }; /* THE PROCESS'S PID IS DETERMINED BY CALLING SYS$GETJPIW */ status = sys$getjpiw( 0, 0, 0, itemlist, 0, 0, 0 ); if ( !(status & STS$M_SUCCESS) ) { edx_signal( 1, &status ); fmtoutstr( 1, &status ); return(status); } /* 00000000 of the output filename is replaced by the process's PID number. */ status = ots$cvt_l_tz( &pid, &pid_desc, 8 ); if ( !(status & STS$M_SUCCESS) ) { edx_signal( 1, &status ); fmtoutstr( 1, &status ); return(status); } /* SOR$PASS_FILES is called, passing the input filename from INSTR, and the output filename we created. */ status = sor$pass_files( &instr_desc, &outfile_desc ); if ( !(status & STS$M_SUCCESS) ) { edx_signal( 1, &status ); fmtoutstr( 1, &status ); return(status); } /* If success, then our generated output filename is copied to OUTSTR (for temporary storage). */ sig_array[0] = SUCCESS; sig_array[1] = outfile_desc.dsc$w_length; sig_array[2] = outfile_desc.dsc$a_pointer; fmtoutstr( 3, &sig_array ); return(status); } /*----------------------------------------------------------------------------- SORT_POSTPARSE Functional Description: Extracts information from CLI about the previously parsed SORT command. Builds and item list of this information and calls SOR$BEGIN_SORT. Calling Sequence: status = sort_postparse( sort_type ); Argument inputs: sort_type - (0 or 1 by value) 0 = using file sort. 1 = using record sort. Outputs: Status. Signaled if bad. Implicit: It is assumed a SORT command line was preveously parsed by SORT_PREPARSE. Outline: 1. Key_Buffer is filled in depending upon qualifiers found in the command string. The key_buffer itemlist is filled from the top down, with a new item added whenever a /KEYn sort key qualifier is found. 1a. KEY.--- qualifiers are examined 1b. KEYn.--- qualifiers are examined with n = 1-9 2. If no keys were given then create 1 key with default values: ascending, offset=0, keysize = {maximum} and check for qualifiers /DESCENSING, /REVERSE, /START. 3. Check for qualifiers /STABLE, /EBCDIC, /MULTINATIONAL, /NODUPLICATES 4. SOR$BEGIN_SORT is called to initialize the sort Memory Map: Structure of key_buffer: We allocate enough storage for 10 keys, (/KEY and /KEY1 - /KEY9) the first word is the number of keys actually filled in. --------------------- |number of keys used| ----------------------------------------- | order | type | \ ----------------------------------------- key 0 | length | offset | / ----------------------------------------- | order | type | \ ----------------------------------------- key 1 | length | offset | / ----------------------------------------- . . . . . . ----------------------------------------- | order | type | \ ----------------------------------------- key 9 | length | offset | / ----------------------------------------- Notes: The SOR$M_... symbols are defined in the SYS$LIBRARY:SORTSHR.EXE shareable image. The linker should resolve these symbols automatically, as part of its search through the images in the system shareable image library SYS$LIBRARY:IMAGELIB.OLB. */ int sort_postparse( int sort_type ) { int i; int position; int size; int sort_options; int lrl; /* longest record length */ int status; short int value_len; char value[255]; char keyn[] = { "KEYn" }; char keyn_position[] = { "KEYn.POSITION" }; char keyn_size[] = { "KEYn.SIZE" }; char keyn_descending[] = { "KEYn.DESCENDING" }; char keyn_reverse[] = { "KEYn.REVERSE" }; $DESCRIPTOR(value_desc,value); $DESCRIPTOR(keyn_desc,keyn); $DESCRIPTOR(keyn_position_desc,keyn_position); $DESCRIPTOR(keyn_size_desc,keyn_size); $DESCRIPTOR(keyn_descending_desc,keyn_descending); $DESCRIPTOR(keyn_reverse_desc,keyn_reverse); struct { short int numkeys; struct { short type; short order; short offset; short length; } key[10]; } key_buffer; i = 0; /* initialize */ /* CHECK FOR KEY QUALIFIER */ if ( cli$present(s_descrip("KEY")) & STS$M_SUCCESS ) /* Test for KEY, then test for KEY1 - KEY9 */ { key_buffer.key[0].type = DSC$K_DTYPE_T; /* Fill in Type is text characters */ /* CHECK FOR KEY.DESCENDING AND/OR KEY.REVERSE (IDENTICAL) */ if ( ( cli$present(s_descrip("KEY.DESCENDING")) & STS$M_SUCCESS ) /* Test for KEY.DESCENDING */ ||( cli$present(s_descrip("KEY.REVERSE")) & STS$M_SUCCESS ) ) /* or for REVERSE (identical function) */ key_buffer.key[0].order = DESCENDING; /* set key descending (same as reverse) */ else key_buffer.key[0].order = ASCENDING; /* else set key ascending */ /* GET KEY.POSITION */ cli$get_value( s_descrip("KEY.POSITION"), &value_desc, &value_len ); lib$cvt_dtb( value_len, /* CONVERT STRING TO NUMBER. length of string to convert, */ value_desc.dsc$a_pointer, /* string by reference, */ &position ); /* result by reference (longword). */ key_buffer.key[0].offset = position - 1; /* Convert position to offset */ /* GET KEY.SIZE */ cli$get_value( s_descrip("KEY.SIZE"), &value_desc, &value_len ); lib$cvt_dtb( value_len, /* CONVERT STRING TO NUMBER. length of string to convert, */ value_desc.dsc$a_pointer, /* string by reference, */ &size ); /* result by reference (longword). */ key_buffer.key[0].length = size; /* Convert longword to word */ i = 1; /* count of # of keys we have accumulated so far */ } /* CHECK FOR KEYn QUALIFIERS */ for ( keyn[3] = '1'; keyn[3] <= '9'; ++keyn[3] ) /* keyn counts from "KEY1" to "KEY9" */ { /* CHECK FOR KEYn QUALIFIER */ if ( cli$present(&keyn_desc) & STS$M_SUCCESS ) /* Test for KEY1 - KEY9 */ { key_buffer.key[i].type = DSC$K_DTYPE_T; /* Fill in Type is text characters */ /* CHECK FOR KEYn.DESCENDING AND/OR KEYn.REVERSE (IDENTICAL) */ keyn_descending[3] = keyn[3]; /* make KEYn_DESCENDING into KEY1_DESCENDING - KEY9_DESCENDING */ keyn_reverse[3] = keyn[3]; /* make KEYn_REVERSE into KEY1_REVERSE - KEY9_REVERSE */ if ( ( cli$present(&keyn_descending_desc) & STS$M_SUCCESS ) /* Test for KEY.DESCENDING */ ||( cli$present(&keyn_reverse_desc) & STS$M_SUCCESS ) ) /* or for REVERSE (identical function) */ key_buffer.key[i].order = DESCENDING; /* set key descending (same as reverse) */ else key_buffer.key[i].order = ASCENDING; /* else set key ascending */ /* GET KEYn.POSITION */ keyn_position[3] = keyn[3]; /* make KEYn_POSITION into KEY1_POSITION - KEY9_POSITION */ cli$get_value( &keyn_position_desc, &value_desc, &value_len ); lib$cvt_dtb( value_len, /* CONVERT STRING TO NUMBER. length of string to convert, */ value_desc.dsc$a_pointer, /* string by reference, */ &position ); /* result by reference (longword). */ key_buffer.key[i].offset = position - 1; /* Convert position to offset */ /* GET KEYn.SIZE */ keyn_size[3] = keyn[3]; /* make KEYn_SIZE into KEY1_SIZE - KEY9_SIZE */ cli$get_value( &keyn_size_desc, &value_desc, &value_len ); lib$cvt_dtb( value_len, /* CONVERT STRING TO NUMBER. length of string to convert, */ value_desc.dsc$a_pointer, /* string by reference, */ &size ); /* result by reference (longword). */ key_buffer.key[i].length = size; /* Convert longword to word */ ++i; /* Increment key_buffer index */ } }/*end for keyn[3]='1'-'9'*/ /* DONE PARSING KEYS. NOW APPLY DEFAULTS (if we didn't get anything) */ if (i == 0) /* IF NO SORT QUALIFIERS */ { /* THEN USE DEFAULT VALUES */ key_buffer.key[0].type = DSC$K_DTYPE_T; /* Fill in Type is text characters */ key_buffer.key[0].order = ASCENDING; /* sort defaults to ascending */ key_buffer.key[0].offset = 0; /* sort starts by default at offset 0 */ key_buffer.key[0].length = 65535; /* key length defaults to 65535 (as big as we can go) */ if ( ( cli$present(s_descrip("DESCENDING")) & STS$M_SUCCESS ) /* Test for /DESCENDING */ ||( cli$present(s_descrip("REVERSE")) & STS$M_SUCCESS ) ) /* or for /REVERSE (identical function) */ key_buffer.key[0].order = DESCENDING; /* set key descending (same as reverse) */ else key_buffer.key[0].order = ASCENDING; /* else set key ascending */ if ( cli$present(s_descrip("START")) & STS$M_SUCCESS ) /* Test for /START */ { cli$get_value( s_descrip("START"), &value_desc, &value_len ); /* Check for /START */ lib$cvt_dtb( value_len, /* CONVERT STRING TO NUMBER. length of string to convert, */ value_desc.dsc$a_pointer, /* string by reference, */ &position ); /* result by reference (longword). */ key_buffer.key[0].offset = position - 1; /* Convert position to offset */ } i = 1; /* Count of number of keys is 1 */ }/*end if i=0*/ /* NOW PARSE FOR SORT_OPTIONS */ sort_options = SOR$M_NOSIGNAL; /* start with this by default */ if ( cli$present(s_descrip("STABLE")) & STS$M_SUCCESS ) /* Test for /STABLE */ sort_options |= SOR$M_STABLE; /* Set stable bit */ if ( cli$present(s_descrip("EBCDIC")) & STS$M_SUCCESS ) /* Test for /EBCDIC */ sort_options |= SOR$M_EBCDIC; /* Set EBCDIC bit */ if ( cli$present(s_descrip("MULTINATIONAL")) & STS$M_SUCCESS ) /* Test for /MULTINATIONAL */ sort_options |= SOR$M_MULTI; /* Set MULTINATIONAL bit */ if ( cli$present(s_descrip("DUPLICATES")) == CLI$_NEGATED ) /* Test for /NODUPLICATES */ sort_options |= SOR$M_NODUPS; /* Set noduplicates bit */ if (sort_type & RECORD_SORT) lrl = SRT_MAXLRL; /* length of longest line if using record sort */ else lrl = 0; /* if using file sort leave LRL=0 */ /* CALL SOR$BEGIN_SORT */ key_buffer.numkeys = i; /* number of keys we have defined */ status = sor$begin_sort( &key_buffer, &lrl, &sort_options ); if ( !(status & STS$M_SUCCESS) ) edx_signal(1,&status); /* signal error */ return(status); /* return sor$begin_sort status */ } /*---------------------------------------------------------------------------- SORT_DO_FILE Functional Description: Perform actual sort. Calling Sequence: sort_do_file(); Implicit inputs: It is assumed that SOR$BEGIN_SORT has already been called (by SORT_POSTPARSE). Outputs: OUTSTR has already been set by SORT_PASSFILES with success code of 1 and string containing ouput filename. This will be used as our return string unless an error occurs here, in which case we remake OUTSTR with our own error information. Outline: 1. SOR$SORT_MERGE is called to sort file creating new output file 2. SOR$END_SORT is called to clean up */ void sort_do_file() { int status; status = sor$sort_merge(); /* DO SORT */ if ( !(status & STS$M_SUCCESS) ) { edx_signal( 1, &status ); fmtoutstr( 1, &status ); } sor$end_sort(); } /* OUTSTR was set previously */ /*-------------------------------------------------------------------------- SORT_RELEASE_REC Functional Description: Pass a record to SORT when using record sort. Calls SOR$RELEASE_REC Calling Sequence: sort_release_rec(); Inputs: INSTR - record being released to SORT Outputs: Status in OUTSTR: 0 = error 1 = success 2 = line too long SRT_MAXLRL is maximum length of record we can do via record sort */ void sort_release_rec() { int status; status = sor$release_rec( &instr_desc ); if (status & STS$M_SUCCESS) fmtoutstr( 1, &SUCCESS ); else { if (status == SOR$_BAD_LRL) /* Check for line too long status */ fmtoutstr( 1, &2 ); /* Set return status to 2 (line too long) */ else { edx_signal( 1, &status ); fmtoutstr( 1, &FAILURE ); } } } /*--------------------------------------------------------------------------- SORT_RETURN_REC Functional Description: Returns a record when using record sort Calling Sequence: sort_return_rec(); Outputs: OUTSTR - returned record. ----------------------------------------------------------------------------*/ void sort_return_rec() { int status; int sig_array[3]; char buffer[SRT_MAXLRL]; $DESCRIPTOR(buffer_desc,buffer); status = sor$return_rec( &buffer_desc, &buffer_desc.dsc$w_length ); /* Get next string (in sorted order) */ if ( !(status & STS$M_SUCCESS) && (status != SS$_ENDOFFILE) ) edx_signal( 1, &status ); sig_array[0] = status; /* retcode */ sig_array[1] = buffer_desc.dsc$w_length; /* length of output string */ sig_array[2] = buffer_desc.dsc$a_pointer; /* address of output string */ fmtoutstr( 3, &sig_array ); /* format output string */ } /* ============================================================================== LOCK FILES ============================================================================== Files may be locked to prevent others from modifying or using tha file. If you are making changes to a file and you don't want anyone else to edit that file while you're making changes to it, you can lock that file. Files are locked by opening them with noshare attributes. A singularly linked list of locked files is kept. locked_files_head is the beginning of this list and points to the first node in the list or is NULL if there are no locked files. Each node in the list contains: 1. A pointer to the next node (or NULL if this is the last node in the list) 2. A FAB block identifying the file (required by $RMS) 3. A NAM block giving the full filename of the file 4. An expanded_file_name buffer (attached to the NAM block) 5. A resultant_file_name buffer (attached to the NAM block) .SUBTITLE LOCK_FILE Functional Description: This routine locks a file to prevent others from editing that file by opening that file with a noshare attribute. Calling Sequence: lock_file(); Inputs: instr_desc = Name of file to lock (by descriptor). Outputs: OUTSTR = return status Outline: 1. A block of memory of struct 'locked_file' is allocated and filled in. 2. If the file is successfully opened the new block of memory is added to the end of the 'locked_files' list pointed to by locked_files_head. -----------------------------------------------------------------------------*/ void lock_file() { int status, sig_array[6]; struct locked_file *locked_files_ptr, *newnodeptr, *prelnkptr, *curlnkptr; /* Allocate memory for NEWNODE block */ newnodeptr = (struct locked_file *) malloc ( sizeof(struct locked_file) ); if (newnodeptr == 0) { edx_signal(1, byref(EDX__MEMERR) ); return; } /* Initialize FAB block */ newnodeptr->fab = cc$rms_fab; newnodeptr->fab.fab$b_shr = FAB$M_NIL; newnodeptr->fab.fab$l_nam = &newnodeptr->nam; newnodeptr->fab.fab$l_fna = instr_desc.dsc$a_pointer; newnodeptr->fab.fab$b_fns = instr_desc.dsc$w_length; /* Initialize NAM block */ newnodeptr->nam = cc$rms_nam; newnodeptr->nam.nam$l_esa = newnodeptr->expanded_file_name; newnodeptr->nam.nam$b_ess = NAM$C_MAXRSS; newnodeptr->nam.nam$l_rsa = newnodeptr->resultant_file_name; newnodeptr->nam.nam$b_rss = NAM$C_MAXRSS; /* open the file */ status = sys$open( &newnodeptr->fab ); if (status & STS$M_SUCCESS) { /* insert newnode at the end of the tree */ newnodeptr->next = 0; if (locked_files_head == 0) locked_files_head = newnodeptr; else { for ( locked_files_ptr = locked_files_head; /* Go to end of linked list of locked_files */ locked_files_ptr->next != 0; locked_files_ptr = locked_files_ptr->next ); locked_files_ptr->next = newnodeptr; } /* signal success */ sig_array[0] = EDX__LOCKED; /* Successfully locked file */ sig_array[1] = 2; /* two FAO arguments */ sig_array[2] = newnodeptr->nam.nam$b_rsl; /* Resultant filename size */ sig_array[3] = newnodeptr->nam.nam$l_rsa; /* Address of resultant filename string */ edx_signal( 4, sig_array ); /* Signal success message */ fmtoutstr( 1, &SUCCESS ); /* format return status */ } else { /* ELSE ERROR OPENING FILE. SIGNAL ERROR, RELEASE MEMORY */ if ( (status == RMS$_FLK) /* If the error was 'file locked by another user' */ && (srch_locked_files(&prelnkptr,&curlnkptr) == EDX__LOCKED) ) /* and we are the ones who have it locked */ { sig_array[0] = EDX__ALK; sig_array[1] = 2; /* two FAO arguments */ sig_array[2] = curlnkptr->nam.nam$b_esl; /* Expanded filename size */ sig_array[3] = curlnkptr->nam.nam$l_esa; /* Address of expanded filename string */ edx_signal( 4, sig_array ); /* Signal message */ } else { sig_array[0] = EDX__NOLOCK; /* file not locked */ sig_array[1] = 2; /* two FAO arguments */ sig_array[2] = newnodeptr->nam.nam$b_esl; /* Expanded filename size */ sig_array[3] = newnodeptr->nam.nam$l_esa; /* Address of expanded filename string */ sig_array[4] = status; /* error code */ sig_array[5] = 0; /* zero FAO arguments for error */ edx_signal( 6, sig_array ); /* Signal message */ }/*endif we already have file locked*/ #if DEBUG /* DEBUG. Fill memory we're about to release with 'FF' making it unusable. */ /* Then make sure this node has been removed from the list. */ { int i; char *p; struct locked_file *ptr; for (i=0, p=newnodeptr; i < sizeof(struct locked_file); ++i, ++p) *p = 0xFF; if (locked_files_head != 0) for ( ptr = locked_files_head; ptr->next != 0; ptr = ptr->next ); } #endif free(newnodeptr); /* release memory */ fmtoutstr( 1, &FAILURE ); /* format return status */ }/*endif status=sys$open(&newnodeptr->fab)*/ } /*---------------------------------------------------------------------------- .SUBTITLE UNLOCK_FILE Functional Description: This procedure unlocks the specified file locked by routine LOCK_FILE by closing it and deallocating the memory for the locked_file BLOCK. Calling Sequence: unlock_file(); Inputs: instr_desc = Name of file to unlock (by descriptor). Outputs: OUTSTR = {SUCCESS=1, FAILURE=0} Outline: 1. We search the linked list locked_files_head for the specified filename. If found we close the file and remove the block from the linked list. If not found we signal the error. ---------------------------------------------------------------------------*/ void unlock_file() { int status; int sig_array[6]; int outfile_len; char outfile[NAM$C_MAXRSS]; struct locked_file *prelnkptr, /* Previous Locked_files Block */ *curlnkptr; /* Block Containing Filename */ /* Find matching locked_file BLOCK for INSTR filename */ status = srch_locked_files( &prelnkptr, &curlnkptr );/* Search for BLOCK containing filename */ if (status == EDX__LOCKED) /* If successful then prelnk = Address of previous locked_files BLOCK (possibly zero) */ { /* Found match. Close file and return memory */ /* curlnkptr = Address of locked_file BLOCK to close */ /* prelnkptr = Address of previous locked_file BLOCK (maybe 0) */ status = sys$close( &curlnkptr->fab ); /* close file */ if (status & STS$M_SUCCESS) { sig_array[0] = EDX__UNLOCKED; /* Signal success message */ sig_array[1] = 2; /* two FAO arguments */ sig_array[2] = curlnkptr->nam.nam$b_rsl; /* Expanded filename size */ sig_array[3] = curlnkptr->nam.nam$l_rsa; /* Address of expanded filename string */ edx_signal( 4, sig_array ); /* Signal message */ if (prelnkptr != 0) prelnkptr->next = curlnkptr->next; /* prevlnkptr points to previous BLOCK (may be 0 if no previous BLOCK) */ else locked_files_head = curlnkptr->next; /* was no previous link. Set header to point to next BLOCK */ #if DEBUG /* DEBUG. Fill memory we're about to release with 'FF' making it unusable. */ /* Then make sure this node has been removed from the list. */ { int i; char *p; struct locked_file *ptr; for (i=0, p=curlnkptr; i < sizeof(struct locked_file); ++i, ++p) *p = 0xFF; if (locked_files_head != 0) for ( ptr = locked_files_head; ptr->next != 0; ptr = ptr->next ); } #endif free(curlnkptr); /* free up memory */ fmtoutstr( 1, &SUCCESS ); return; } else /* else error closing file. Signal error and return */ { edx_signal(1,&status); /* signal error */ fmtoutstr( 1, &FAILURE ); return; } } /* else status != EDX_LOCKED */ else if (status == EDX__NOTLOCKED) /* Test for not found failure */ { /* File Not Found in linked list. */ /* Came to end of linked list. No match found. */ /* Reparse input filename not using physical device names to get concealed logical names. */ status = edx_parse( NAM$C_MAXRSS, /* output buffer length (by value) */ outfile, /* output buffer (char array by reference) */ &outfile_len, /* result length (by reference) */ instr_desc.dsc$w_length, /* input length (by value) */ instr_desc.dsc$a_pointer, /* input (by value) */ FALSE ); /* don't use physical device names (by value) */ sig_array[0] = EDX__NOTLOCKED; sig_array[1] = 2; /* two FAO arguments */ sig_array[2] = outfile_len; /* Expanded filename size */ sig_array[3] = outfile; /* Address of expanded filename string */ edx_signal( 4, sig_array ); /* Signal message */ fmtoutstr( 1, &FAILURE ); /* format return status */ return; } else /* else error was something else. Signal error and return. */ { edx_signal(1,&status); fmtoutstr( 1, &FAILURE ); /* format return status */ return; } } /*---------------------------------------------------------------------------- .SUBTITLE EDX_CKFILK Functional Description: This procedure searches the linked list of filenames locked by LOCK_FILE for a specified filename. If the file is found it returns #1 in RETCODE to TPU. If the file is not found it returns #0 in RETCODE to TPU. Calling Sequence: edx_ckfilk(); Inputs: instr_desc = Name of file to check for (by descriptor). Outputs: OUTSTR = success (file found in locked_list), failure (file not found in locked_list). Outline: 1. Call SRCH_LOCKED_FILES. It does all the work. We just check the return status and set RETCODE accordingly. -------------------------------------------------------------------------*/ void edx_ckfilk() { int status; struct locked_file *prelnkptr, *curlnkptr; status = srch_locked_files(&prelnkptr,&curlnkptr); /* Search for BLOCK containing filename */ if (status == EDX__LOCKED) fmtoutstr( 1, &SUCCESS ); else if (status == EDX__NOTLOCKED) fmtoutstr( 1, &FAILURE ); else fmtoutstr( 1, &status ); } /*------------------------------------------------------------------------- .SUBTITLE SRCH_LOCKED_FILES Functional Description: This procedure searches the linked list of filenames locked by LOCK_FILE for a specified filename. Calling Sequence: struct locked_file *prelnkptr, *curlnkptr; status = srch_locked_files( &prelnkptr, &curlnkptr ); Inputs: instr_desc = Name of file to check for (by descriptor). Returns: status = EDX__LOCKED if successful, EDX__NOTLOCKED if failure, or error status code. prelnkptr = Base address of previous LOCKED_FILES BLOCK curlnkptr = Base address of current LOCKED_FILES BLOCK which has target filename. Outline: 1. The input filename is parsed to give a full filename 2. The filename of the first LOCKED_FILES BLOCK is parsed 3. A comparison of the two filenames is made If they match we've found our BLOCK If they don't match we try the next BLOCK If we come to the end of the linked list with no match we return with the error EDX__NOTLOCKED The target filename is matched with each filename in the linked list only up to the target filename length. The target filename might not specify a version number. If multiple versions of the same file are locked, and no version to unlock is specified, we will unlock the first matching filename we come across, which might be a version different from the default the user wanted to get. I have a vague memory that this was done on purpose to make unlocking files easier when you didn't know the version number. Programming notes: a = prelnkptradr; ! Address of prelnkptr. (value of prelnkptradr) b = *prelnkptradr; ! Value of prelnkptr. (address of blk) c = (*prelnkptradr)->next; ! c = blk.next d = &prelnkptradr; ! Address of {address of prelnkptr} in call ! frame argument list. (SHOW SYMBOL/ADDRESS ! prelnkptradr). ---------------------------------------------------------------------------*/ int srch_locked_files( struct locked_file **prelnkptradr, /* pointer to a pointer to a structure of type locked_file */ struct locked_file **curlnkptradr ) { int status; int target_filename_length; int block_filename_length; char target_filename[NAM$C_MAXRSS]; char block_filename[NAM$C_MAXRSS]; /* Parse target filename */ status = edx_parse( NAM$C_MAXRSS, /* output buffer length (by value) */ target_filename, /* output buffer (char array by reference) */ &target_filename_length, /* output length (by reference) */ instr_desc.dsc$w_length, /* input length (by value) */ instr_desc.dsc$a_pointer, /* input buffer (char array by reference) */ TRUE ); /* use physical device names (by value) */ if ( !(status & STS$M_SUCCESS) ) return(status); /* Else return error */ /* Go through linked list of locked file BLOCKS */ *curlnkptradr = locked_files_head; /* pointer to structure of type locked_file */ *prelnkptradr = 0; /* pointer to structure of type locked_file = NULL */ while (*curlnkptradr != 0) { status = edx_parse( NAM$C_MAXRSS, /* Parse output filespec (length of output buffer by value) */ block_filename, /* out buffer (char array by reference) */ &block_filename_length, /* result length (by reference) */ (*curlnkptradr)->nam.nam$b_rsl, /* input length (by value) */ (*curlnkptradr)->nam.nam$l_rsa, /* input buffer (char array by reference) */ TRUE ); /* use physical device names (by value) */ if (strncmp(target_filename,block_filename,target_filename_length) == 0) /* found match */ return( EDX__LOCKED ); /* normal exit */ *prelnkptradr = *curlnkptradr; /* go to next BLOCK in linked list */ *curlnkptradr = (*curlnkptradr)->next; } return( EDX__NOTLOCKED ); /* if we drop out the bottom */ } /* then block not found */ /* *prelnkptradr points to last block in linked list */ /* *curlnkptradr = 0 */ /*--------------------------------------------------------------------------- .SUBTITLE EDX_PARSE Functional Description: This routine accepts a filespec as input and parses it returning the full file specification using physical device names. Calling Sequence: status = edx_parse( output_filename_buffer_length, output_filename_buffer, output_filename_length, input_filename_buffer_length, input_filename_buffer, pdn ); Argument inputs: int output_filename_buffer_length, = (read) length of output buffer char *output_filename_buffer, = (write) address of output buffer int output_filename_length, = (write) length of resulting filename (only chars up to this length are valid) int input_filename_buffer_length, = (read) length of input buffer char *input_filename_buffer, = (read) address of input buffer pdn = (read) If TRUE then use physical device names. Outputs: status = Parse status Outline: 1. FAB and NAM blocks are initialized 2. The filespec is parsed -------------------------------------------------------------------------*/ int edx_parse( int output_filename_buffer_length, /* read, by value */ char *output_filename_buffer, /* write, char array by reference */ int *output_filename_length, /* write, by reference (has to be) */ int input_filename_buffer_length, /* read, by value */ char *input_filename_buffer, /* read, char array by reference */ int pdn ) /* read, by value. Logical: use physical device names */ { int status; struct NAM nam; struct FAB fab; /* Initialize FAB block */ fab = cc$rms_fab; fab.fab$l_nam = &nam; fab.fab$l_fna = input_filename_buffer; fab.fab$b_fns = input_filename_buffer_length; /* Initialize NAM block */ nam = cc$rms_nam; nam.nam$l_esa = output_filename_buffer; nam.nam$b_ess = output_filename_buffer_length; nam.nam$b_nop = NAM$M_SYNCHK; if (pdn) nam.nam$b_nop |= NAM$M_NOCONCEAL; /* Parse the filename */ status = sys$parse( &fab ); /* Parse the filename */ *output_filename_length = nam.nam$b_esl; /* resultant expanded filename length (convert byte to int) */ return( status ); /* sys$parse status */ } /* ============================================================================== MISCELLANEOUS ============================================================================== .SUBTITLE EDX_SETDEF Functional Description: This procedure changes a user's default directory. Calling Sequence: edx_setdef(); Argument inputs: INSTR = Address of descriptor of string containing new directory to go to Outline: 1. The FAB and NAM blocks are initialized 2. The filespec is parsed 3. The node and disk are extracted and SYS$DISK is defined 4. Call SYS$SETDDIR 5. Check return status and signal if error */ void edx_setdef() { int status; int length; struct NAM nam; struct FAB fab; char output_filename[BUFLEN]; $DESCRIPTOR(output_filename_desc,output_filename); $CONST_DESCRIP(sys$disk_desc,"SYS$DISK"); struct dsc$descriptor_s disk_desc = { 0, DSC$K_DTYPE_T, DSC$K_CLASS_S, 0 }; /* Initialize FAB block */ fab = cc$rms_fab; fab.fab$l_nam = &nam; fab.fab$l_fna = instr_desc.dsc$a_pointer; fab.fab$b_fns = instr_desc.dsc$w_length; /* Initialize NAM block */ nam = cc$rms_nam; nam.nam$l_esa = output_filename_desc.dsc$a_pointer; nam.nam$b_ess = output_filename_desc.dsc$w_length; /* Parse the filename */ status = sys$parse( &fab ); /* Parse the filename */ if ( !(status & STS$M_SUCCESS) ) { edx_signal( 1, &status ); fmtoutstr( 1, &FAILURE ); return; } /* Set default disk by defining SYS$DISK as NODE::DISK: */ disk_desc.dsc$w_length = length = nam.nam$b_node + nam.nam$b_dev; /* Length of NODE::DISK: */ if (length > 0) /* otherwise DISK not specified */ { disk_desc.dsc$a_pointer = nam.nam$l_node; status = lib$set_logical( &sys$disk_desc, &disk_desc ); if ( !(status & STS$M_SUCCESS) ) { edx_signal( 1, &status ); fmtoutstr( 1, &FAILURE ); return; } } /* Set default directory */ status = sys$setddir( &instr_desc, 0, 0 ); if (status & STS$M_SUCCESS) fmtoutstr( 1, &SUCCESS ); else { edx_signal( 1, &status ); fmtoutstr( 1, &FAILURE ); } } /*--------------------------------------------------------------------------- .SUBTITLE SET LOGICAL Functional Description: Defines a logical name. The logical name is created in supervisor mode and placed in the LNM$PROCESS table. Calling Sequence: set_logical(); Global inputs: instr_desc = Input string descriptor. String is of the form: "log-nam value" It should be a substring of the full DCL type command "DEFINE log-nam value". log-nam = Logical name to be defined or redefined. value = Value to be given to the logical name. Outline: 1. The input string is parsed. A descriptor for the substring "log-nam" and a descriptor for the substring "value" are made. 2. LIB$SET_LOGICAL is called to define the logical name. The return status checked and signaled if there is an error. ------------------------------------------------------------------------------*/ void set_logical() { int status; struct dsc$descriptor_s lognam_desc = { 0, DSC$K_DTYPE_T, DSC$K_CLASS_S, 0 }; struct dsc$descriptor_s value_desc = { 0, DSC$K_DTYPE_T, DSC$K_CLASS_S, 0 }; char *p, *end; /* PARSE OFF LOG-NAM */ end = instr_desc.dsc$a_pointer + instr_desc.dsc$w_length; for ( p = instr_desc.dsc$a_pointer; p < end && *p != SPACE; ++p ); /* Find space character - End of "log-nam" */ lognam_desc.dsc$w_length = p - instr_desc.dsc$a_pointer; /* Length of "log-nam" */ lognam_desc.dsc$a_pointer = instr_desc.dsc$a_pointer; /* PARSE OFF VALUE */ for ( ; p < end && *p == SPACE; ++p ); /* Find first non-space character - Beginning of "value" */ value_desc.dsc$w_length = end - p; /* Length of "value" */ value_desc.dsc$a_pointer = p; /* Now points to beginning of VALUE (or one past end of string in which case length should be 0 */ status = lib$set_logical( &lognam_desc, &value_desc ); /* Call LIB$SET_LOGICAL */ if ( status & STS$M_SUCCESS) fmtoutstr( 1, &SUCCESS ); else { /* Process error in creating logical name */ edx_signal( 1, &status ); fmtoutstr( 1, &FAILURE ); } } /*--------------------------------------------------------------------------- .SUBTITLE SET_SYMBOL Functional Description: Creates a DCL symbol. Calling Sequence: set_symbol(); Global inputs: instr_desc = Input string descriptor. String is of the form: "symbol-name0equivalence0tblind" where 0 represents an ascii 00 character. symbol-name = Name to be defined or redefined. expression = Expression to be given to the symbol. tblind = Indicator of the table which will contain the defined symbol. 1=local, 2=global. (See LIB$SET_SYMBOL) Outline: 1. The input string is parsed. A descriptor for the substring "symbol-name", a descriptor for the substring "expression", and the value of tblind is extracted. 2. LIB$SET_SYMBOL is called to create the DCL symbol. The return status is checked and signaled if there is an error. ---------------------------------------------------------------------------*/ void set_symbol() { struct dsc$descriptor_s symnam_desc = { 0, DSC$K_DTYPE_T, DSC$K_CLASS_S, 0 }; struct dsc$descriptor_s expres_desc = { 0, DSC$K_DTYPE_T, DSC$K_CLASS_S, 0 }; char *p, *end; int tblind; int status; /* PARSE OFF SYMBOL-NAME */ end = instr_desc.dsc$a_pointer + instr_desc.dsc$w_length; symnam_desc.dsc$a_pointer = instr_desc.dsc$a_pointer; /* Starting address of "symbol-name" */ for ( p = symnam_desc.dsc$a_pointer; p < end && *p != 0x00; ++p ); /* Find first null in string - End of "symbol-name" */ symnam_desc.dsc$w_length = p - symnam_desc.dsc$a_pointer; /* Length of "symbol-name" */ /* PARSE OFF EQUIVALENCE */ ++p; /* Skip over NULL */ expres_desc.dsc$a_pointer = p; /* Starting address of "equivalence" */ for ( ; p < end && *p != 0x00; ++p ); /* Find first non-space character - Beginning of "value" */ expres_desc.dsc$w_length = p - expres_desc.dsc$a_pointer; /* Length of "equivalence" */ /* PARSE OFF TBLIND */ ++p; /* Skip over NULL */ if (p >= end) { /* fail with internal error. tblind not specified. */ /* edx_signal( 1, &status ); */ fmtoutstr( 1, &FAILURE ); return; } tblind = *p - '0'; /* convert character digit to integer value */ /* Call LIB$SET_SYMBOL */ status = lib$set_symbol( &symnam_desc, &expres_desc, &tblind ); if (status & STS$M_SUCCESS) { fmtoutstr( 1, &SUCCESS ); } else { /* Process error in setting symbol */ edx_signal( 1, &status ); fmtoutstr( 1, &FAILURE ); } } /*--------------------------------------------------------------------------- .SUBTITLE SHOW LOGICAL Functional Description: Translates a logical name Calling Sequence: show_logical(); Global inputs: instr_desc = Logical name to translate (by descriptor). Outputs: outstr_desc = translation of logical name (by descriptor). Outline: 1. Itemlist for call to SYS$TRNLNM is initialized 2. SYS$TRNLNM is called to obtain logical name translation -----------------------------------------------------------------------------*/ void show_logical() { int status; unsigned short retlen; int sig_array[3]; char buffer[BUFLEN]; $CONST_DESCRIP(lnm_table,"LNM$FILE_DEV"); item_list_3 itemlist[2] = { { BUFLEN, LNM$_STRING, buffer, &retlen }, { 0, 0, 0, 0 } }; status = sys$trnlnm( &LNM$M_CASE_BLIND, &lnm_table, &instr_desc, 0, itemlist); /* Translate logical name */ if (status & STS$M_SUCCESS) { sig_array[0] = SUCCESS; sig_array[1] = retlen; sig_array[2] = buffer; fmtoutstr( 3, &sig_array ); } else { edx_signal( 1, &status ); fmtoutstr( 1, &FAILURE ); } } /*----------------------------------------------------------------------------- .SUBTITLE SHOW SYMBOL Functional Description: Translates a DCL symbol Calling Sequence: show_symbol(); Global inputs: instr_desc = Symbol name to translate (by descriptor). Outputs: outstr_desc = translation of symbol (by descriptor). Outline: 1. LIB$GET_SYMBOL is called to obtain the symbol translation ---------------------------------------------------------------------------*/ void show_symbol() { int status; int tblind; int sig_array[3]; char buffer[BUFLEN]; $DESCRIPTOR(buffer_desc,buffer); status = lib$get_symbol( &instr_desc, &buffer_desc, &buffer_desc.dsc$w_length, &tblind); /* Translate DCL symbol */ if (status & STS$M_SUCCESS) { sig_array[0] = SUCCESS; sig_array[1] = buffer_desc.dsc$w_length; sig_array[2] = buffer; fmtoutstr( 3, &sig_array ); /* Copy translation to output */ } else { edx_signal( 1, &status ); fmtoutstr( 1, &FAILURE ); } } /*----------------------------------------------------------------------------- .SUBTITLE DELETE FILE Functional Description: Deletes the specified file. Calling Sequence: delete_file(); Global inputs: instr_desc = Input string descriptor. File to delete. ---------------------------------------------------------------------------*/ void delete_file() { int status; status = lib$delete_file( &instr_desc ); if (status & STS$M_SUCCESS) fmtoutstr( 1, &SUCCESS ); else { edx_signal( 1, &status ); /* Process error deleting file */ fmtoutstr( 1, &FAILURE ); } } /*--------------------------------------------------------------------------- .SUBTITLE TRA_EBC_ASC Functional Description: Translates EBCDIC to ASCII Calling Sequence: tra_ebc_asc(); Global inputs: instr_desc = Input string descriptor. EBCDIC string. Outputs: outstr_desc = ASCII translation of input string (by descriptor). Outline: 1. LIB$TRA_EBC_ASC is called to obtain the translation string ;--*/ void tra_ebc_asc() { int status; int sig_array[3]; char buffer[MAXLEN]; struct dsc$descriptor_s buffer_desc = { instr_desc.dsc$w_length, DSC$K_DTYPE_T, DSC$K_CLASS_S, buffer }; status = lib$tra_ebc_asc( &instr_desc, &buffer_desc ); if (status & STS$M_SUCCESS) { sig_array[0] = SUCCESS; sig_array[1] = instr_desc.dsc$w_length; sig_array[2] = buffer; fmtoutstr( 3, &sig_array); /* Copy translation to output */ } else { edx_signal( 1, &status ); fmtoutstr( 1, &FAILURE ); } } /*----------------------------------------------------------------------------- .SUBTITLE TRA_ASC_EBC Functional Description: Translates ASCII to EBCDIC Calling Sequence: tra_asc_ebc(); Global inputs: instr_desc = Input string descriptor. ASCII string. Outputs: outstr_desc = EBCDIC translation of input string (by descriptor). Outline: 1. LIB$TRA_ASC_EBC is called to obtain the translation string ;--*/ void tra_asc_ebc() { int status; int sig_array[3]; char buffer[MAXLEN]; struct dsc$descriptor_s buffer_desc = { instr_desc.dsc$w_length, DSC$K_DTYPE_T, DSC$K_CLASS_S, buffer }; status = lib$tra_asc_ebc( &instr_desc, &buffer_desc ); if (status & STS$M_SUCCESS) { sig_array[0] = SUCCESS; sig_array[1] = instr_desc.dsc$w_length; sig_array[2] = buffer; fmtoutstr( 3, &sig_array ); /* Copy translation to output */ } else { edx_signal( 1, &status ); fmtoutstr( 1, &FAILURE ); } } /*============================================================================= .SUBTITLE LIBRARIAN Functional Description: Dispatches to the appripriate library routine based on the low word code value n. ^x0007000n - LIBRARY (458752) n = 1. Initialize, open for read, lookup_key n = 2. Return next line of text from module n = 3. Close text library n = 4. Initialize, open for write, lookup_key n = 5. Write next line of text to module n = 6. Write end-of-module record */ #define LBRIOR 1 /* Initialize, Open for read, Lookup_key */ #define LBRIOW 2 /* Initialize, Open for write, Lookup_key */ #define LBRCLO 3 /* Close text library */ #define LBRRNX 4 /* Read next line from module */ #define LBRWNX 5 /* Write next line to module */ #define LBRINS 6 /* Insert (/replace) new module into library */ static int lbr_index; /* Library Control Index */ static int lbr_rfa[2]; /* (Quadword) Current Library Module. 0 if none. */ void librarian() { int incodel; /* Entry code is cased for reentry point */ incodel = (*incode_ptr & 0x0000FFFF); switch (incodel) /* Case entry point to jump to */ { case LBRIOR: lbr_init(LBR$C_READ); break; /* 1 = Initialize, Open for read, Lookup_key */ case LBRIOW: lbr_init(LBR$C_UPDATE); break; /* 2 = Initialize, Open for write, Lookup_key */ case LBRCLO: lbr_close(); break; /* 3 = Close text library */ case LBRRNX: lbr_readnext(); break; /* 4 = Read next line from module */ case LBRWNX: lbr_writenext(); break; /* 5 = Write next line to module */ case LBRINS: lbr_replace(); break; /* 6 = Insert (/replace) new module into library */ default: edx_signal(1, byref(EDX__UNKNCODE) ); return; } } /*---------------------------------------------------------------------------- LBR_INIT Functional Description: Initialize librarian, open text library for read or write access as specified in argument, locate module. Returns: If error parsing INSTR, signal parse erorr and return 0 in OUTSTR status. If error calling LBR$INI_CONTROL, signal error and return 0 in OUTSTR status. If error calling LBR$OPEN, signal error and return 0 in OUTSTR status. If error calling LBR$LOOKUP_KEY: If error was LBR$KEYNOTFND, return 2 in OUTSTR status. This may be OK if inserting a new module. This may not be OK if we're trying to read a module. Any other error: signal error and return 0 in OUTSTR status. If no errors: LBR_RFA = RFA of module found by LBR$LOOKUP_KEY Return 1 in OUTSTR status. Calling Sequence: lbr_init(access); Inputs: access = LBR$C_READ or LBR$C_UPDATE depending upon whether this is open for read access or open for write access. (by value. Passed to LBR$INI_CONTROL as "func" parameter.) INSTR = Contains text library filename followed by a space character followed by the module name to extract INSTR = "<text library filename> <module to extract>" Outline: 1. Initialize librarian - LBR$INI_CONTROL 2. Open text library a. Parse off filename from INSTR b. Call LBR$OPEN 3. Locate module within text library a. Parse off module name b. Call LBR$LOOKUP_KEY 4. Call LBR$SET_LOCATE to set locate mode Note: The LBR$_... symbols are defined in the SYS$LIBRARY:LBRSHR.EXE shareable image. The linker should resolve these symbols automatically, as part of its search through the images in the system shareable image library SYS$LIBRARY:IMAGELIB.OLB. ---------------------------------------------------------------------------*/ void lbr_init(int access) { struct dsc$descriptor_s filnam_desc = { 0, DSC$K_DTYPE_T, DSC$K_CLASS_S, 0 }; struct dsc$descriptor_s modnam_desc = { 0, DSC$K_DTYPE_T, DSC$K_CLASS_S, 0 }; int status; int sig_array[5]; char *p, *end; /* Parse INSTR for filename */ end = instr_desc.dsc$a_pointer + instr_desc.dsc$w_length; filnam_desc.dsc$a_pointer = instr_desc.dsc$a_pointer; /* Starting address of "filename" */ for ( p = filnam_desc.dsc$a_pointer; p < end && *p != 0x20; ++p ); /* Find first SPACE in string - End of "filename" */ filnam_desc.dsc$w_length = p - filnam_desc.dsc$a_pointer; /* Length of "filename" */ /* Parse INSTR for module_name */ modnam_desc.dsc$a_pointer = instr_desc.dsc$a_pointer + filnam_desc.dsc$w_length + 1; /* Starting address of "modulename" */ modnam_desc.dsc$w_length = end - modnam_desc.dsc$a_pointer; /* Length of rest of string */ if (modnam_desc.dsc$w_length <= 0) { edx_signal( 1, byref(EDX__NOMODNAM) ); /* No input module name. */ fmtoutstr( 1, &FAILURE ); return; } /* Initialize library control structure */ status = lbr$ini_control( &lbr_index, &access, &LBR$C_TYP_TXT ); if ( !(status & STS$M_SUCCESS) ) { edx_signal( 1, &status ); fmtoutstr( 1, &FAILURE ); return; } /* Open library file */ status = lbr$open( &lbr_index, &filnam_desc ); if ( !(status & STS$M_SUCCESS) ) { /* Error opening specified text library. Signal error open, then signal error */ sig_array[0] = EDX__ERROPEN; /* Error opening file */ sig_array[1] = 1; /* one FAO argument */ sig_array[2] = &filnam_desc; /* Text library file name */ sig_array[3] = status; edx_signal( 4, &sig_array ); fmtoutstr( 1, &FAILURE ); return; } /* Set record access to locate mode */ status = lbr$set_locate( &lbr_index ); if ( !(status & STS$M_SUCCESS) ) { /* unusual error during lbr$set_locate. Signal error, close library, return 0 */ edx_signal( 1, &status ); status = lbr$close( &lbr_index ); if ( !(status & STS$M_SUCCESS) ) edx_signal( 1, &status ); fmtoutstr( 1, &FAILURE ); return; } /* Look for module in library */ status = lbr$lookup_key( &lbr_index, &modnam_desc, &lbr_rfa ); if ( status == LBR$_KEYNOTFND ) { lbr_rfa[0]=0; lbr_rfa[1]=0; /* no old module in library */ if (access == LBR$C_READ) { sig_array[0] = EDX__MODNOTFND; /* text library module not found */ sig_array[1] = 2; /* two FAO arguments */ sig_array[2] = &modnam_desc; /* Module name */ sig_array[3] = &filnam_desc; /* Text library file name */ sig_array[4] = status; edx_signal( 5, &sig_array ); status = lbr$close( &lbr_index ); if ( !(status & STS$M_SUCCESS) ) edx_signal( 1, &status ); } fmtoutstr( 1, &2 ); /* Key not found status */ } else if ( !(status & STS$M_SUCCESS) ) { edx_signal( 1, &status ); status = lbr$close( &lbr_index ); if ( !(status & STS$M_SUCCESS) ) edx_signal( 1, &status ); fmtoutstr( 1, &FAILURE ); /* error other than 'Key Not Found' */ } else fmtoutstr( 1, &SUCCESS ); /* found key success */ } /*--------------------------------------------------------------------------- LBR_CLOSE Functional Description: Closes text library freeing up internal storage used by librarian. Calling Sequence: lbr_close(); Inputs: LBR_INDEX - set by LBR_INIT Outputs: OUTSTR - status. ---------------------------------------------------------------------------*/ void lbr_close() { int status; status = lbr$close(&lbr_index); /* close library */ if (status & STS$M_SUCCESS) fmtoutstr( 1, &SUCCESS ); /* Normal success */ else { edx_signal( 1, &status ); fmtoutstr( 1, &FAILURE ); } } /*--------------------------------------------------------------------------- LBR_READNEXT Functional Description: Returns next line from text module. Assumed LBR_INIREAD already called to initialize library. Calling Sequence: lbr_readnext(); Inputs: LBR_INDEX - set by LBR_INIREAD Outputs: OUTSTR - Next line from module within text library. ---------------------------------------------------------------------------*/ void lbr_readnext() { int sig_array[3]; struct dsc$descriptor_s outbuf_desc = { 0, DSC$K_DTYPE_T, DSC$K_CLASS_S, 0 }; sig_array[0] = lbr$get_record( &lbr_index, 0, &outbuf_desc ); sig_array[1] = outbuf_desc.dsc$w_length; sig_array[2] = outbuf_desc.dsc$a_pointer; fmtoutstr( 3, sig_array ); } /*--------------------------------------------------------------------------- LBR_WRITENEXT Functional Description: Write next line to text module. Assumed LBR_INIWRITE already called to initialize library. Calling Sequence: lbr_writenext(); Inputs: INSTR - Next line to write to module LBR_INDEX - set by LBR_INIWRITE ---------------------------------------------------------------------------*/ void lbr_writenext() { int status; /* Write next record to module */ status = lbr$put_record( &lbr_index, &instr_desc, &lbr_rfa ); if ( status & STS$M_SUCCESS ) fmtoutstr( 1, &SUCCESS ); else { edx_signal( 1, &status ); fmtoutstr( 1, &FAILURE ); } } /*--------------------------------------------------------------------------- LBR_REPLACE Functional Description: Insert (/replace) text module into library. Usage: The sequence for inserting a text module is as follows: CALL LBR_INIT( LBR$C_UPDATE ) IF (STATUS = 2) THEN ASK USER FOR PERMISSION TO REPLACE EXISTING MODULE; LOOP CALL LBR_WRITENEXT !Pass lines of text to library creating module ENDLOOP CALL LBR_REPLACE !Insert new module into library index !(If previous module existed, it is deleted at this time) Calling Sequence: lbr_replace(); Inputs: LBR_INDEX - Library Control Index. Set by LBR_INIT by LBR$INI_CONTROL LBR_RFA - Current Library Module. Set by LBR_INIT by LBR$LOOKUP_KEY INSTR - Name of text module to insert. Outline: The sequence is similar to the DCL command $ LIBRARY/TEXT/INSERT See INPUTTXT.LIS in facility LIBRAR ---------------------------------------------------------------------------*/ void lbr_replace() { int status; int oldrfa[2]; /* WRITE END-OF-MODULE RECORD */ status = lbr$put_end( &lbr_index ); /* write end-of-module record */ if ( !(status & STS$M_SUCCESS) ) { edx_signal( 1, &status ); lbr_close(); return; } /* SEE IF MODULE NAME ALREADY EXISTS */ status = lbr$lookup_key( &lbr_index, &instr_desc, &oldrfa ); if ( status == LBR$_KEYNOTFND ) { oldrfa[0]=0; oldrfa[1]=0; /* no old module in library */ } else if ( !(status & STS$M_SUCCESS) ) { edx_signal( 1, &status ); status = lbr$close( &lbr_index ); if ( !(status & STS$M_SUCCESS) ) edx_signal( 1, &status ); fmtoutstr( 1, &FAILURE ); /* Key not found status */ return; } /* INSERT NEW MODULE */ status = lbr$replace_key( &lbr_index, /* Library control index (read)*/ &instr_desc, /* key-name (read) */ &oldrfa, /* old-module (read) [note error in VMS 4.4 documentation calls this "write only"] */ &lbr_rfa ); /* new module (read) */ if ( !(status & STS$M_SUCCESS) ) /* on error signal, attempt close, and format bad return status */ { edx_signal( 1, &status ); status = lbr$close( &lbr_index ); if (status & STS$M_SUCCESS) { lbr_rfa[0]=0; lbr_rfa[1]=0; /* no current library module */ } else edx_signal( 1, &status ); fmtoutstr( 1, &FAILURE ); } if ( oldrfa[0] != 0 || oldrfa[1] != 0 ) /* If old module then delete old module */ { status = lbr$delete_data( &lbr_index, &oldrfa ); /* Delete old module */ if ( !(status & STS$M_SUCCESS) ) edx_signal( 1, &status ); } lbr_close(); }