home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 5 Edit / 05-Edit.zip / lxprpas.zip / LXPRPAS.C < prev   
Text File  |  1998-04-20  |  41KB  |  1,003 lines

  1. /*****************************************************************************/
  2. /*                                                                           */
  3. /* LXPRPAS.C  -  A simple LPEX Parser for Pascal files.                      */
  4. /*                                                                           */
  5. /*****************************************************************************/
  6. /*                                                                           */
  7. /* This is an example of a simple programming language parser for the LPEX   */
  8. /* editor.  It will colour the various tokens of a Pascal program in diffe-  */
  9. /* rent colours, as an aid to the programmer.  This code is provided as an   */
  10. /* example of how a parser is written - it should be easy to adapt it to     */
  11. /* handle any of the common block structured programming languages.          */
  12. /*                                                                           */
  13. /* This parser is designed to handle standard Pascal.  Most real Pascal      */
  14. /* implementations have many changes and extensions.                         */
  15. /*                                                                           */
  16. /* To LPEX a parser appears as an editor command.  Indeed, it can be invoked */
  17. /* like any other command by typing its name on the command line.  However,  */
  18. /* it would normally be invoked automatically by the live parsing mechanism. */
  19. /* By convention, commands that are parsers begin with the two letters "PR", */
  20. /* so this Pascal parser is the command "PRPAS".                             */
  21. /*                                                                           */
  22. /* Command Syntax:                                                           */
  23. /*                                                                           */
  24. /*     PRPAS ALL  -  this will cause the parser to process the whole docu-   */
  25. /*                   ment.  It would normally only be used once, when the    */
  26. /*                   document is first loaded, and would normally be invoked */
  27. /*                   from the .LXL load macro.                               */
  28. /*                                                                           */
  29. /*     PRPAS      -  this will cause the parser to process just the current  */
  30. /*                   element.  The live parsing mechanism works by calling   */
  31. /*                   the parser command for each element that is changed.    */
  32. /*                   It ensures that when the parser is called the changed   */
  33. /*                   element is the current element.                         */
  34. /*                                                                           */
  35. /* Return codes:                                                             */
  36. /*                                                                           */
  37. /*    -2  -  Parser table not found                                          */
  38. /*    -3  -  Error reading parser table                                      */
  39. /*    -8  -  Unable to allocate memory                                       */
  40. /*   -11  -  Internal scanning error                                         */
  41. /*   -12  -  Invalid parameters                                              */
  42. /*   The parser may also return any code that the LPEX API calls return.     */
  43. /*                                                                           */
  44. /*****************************************************************************/
  45. /*                                                                           */
  46. /* Operation:                                                                */
  47. /*                                                                           */
  48. /* The parser scans the program lines and recognises the basic tokens of the */
  49. /* language.  It sets different symbolic fonts for the various types of      */
  50. /* token.  The user may then assign these symbolic fonts to colours, in      */
  51. /* order to highlight different items in the program.                        */
  52. /*                                                                           */
  53. /* The following symbolic fonts are used:                                    */
  54. /*                                                                           */
  55. /*     C  -  Comments                                                        */
  56. /*     K  -  Language keywords                                               */
  57. /*     B  -  Built-in functions and procedures                               */
  58. /*     S  -  Language symbols                                                */
  59. /*     A  -  Names (of variables and functions)                              */
  60. /*     N  -  Numbers                                                         */
  61. /*     L  -  Literals                                                        */
  62. /*     E  -  Errors and unexpected characters                                */
  63. /*     _  -  (underscore) Layout space.                                      */
  64. /*                                                                           */
  65. /* In addition, each element has one or more classes associated with it.     */
  66. /* The following classes are used:                                           */
  67. /*                                                                           */
  68. /*     CODE        - Element contains program code                           */
  69. /*     SPACE       - Element is just layout space                            */
  70. /*     FUNCTION    - Element contains a procedure or function declaration    */
  71. /*     COMMENT     - Element contains a comment                              */
  72. /*     OPENCOMMENT - Element contains an unterminated comment                */
  73. /*     ERROR       - Element contains an error.                              */
  74. /*                                                                           */
  75. /* The class OPENCOMMENT is mainly for use by the parser itself.             */
  76. /* The live parsing mechanism presents the parser with single elements to    */
  77. /* examine.  For a simple parser such as this, it is often enough just to    */
  78. /* process that one element in isolation, but there is an exception.  As     */
  79. /* comments can extend across several lines, a change to one line (say, the  */
  80. /* removal of a close comment symbol) might require several lines before and */
  81. /* after the current line to be re-examined as well.  The OPENCOMMENT class  */
  82. /* is used by the parser to determine the parsing limits in such cases.      */
  83. /*                                                                           */
  84. /* To make the Parser adaptable to the various different flavours of Pascal, */
  85. /* its list of keywords and language symbols is held in an external data     */
  86. /* file called LPEXPAS.DAT.  This table is read into storage the first time  */
  87. /* the parser is invoked.  It is only necessary to read it once per LPEX     */
  88. /* session.                                                                  */
  89. /*                                                                           */
  90. /*****************************************************************************/
  91. /*                                                                           */
  92. /*               (C) Copyright IBM Corporation 1989, 1995                    */
  93. /*                                                                           */
  94. /*****************************************************************************/
  95. /*                                                                           */
  96. /* Possible Improvements:                                                    */
  97. /*                                                                           */
  98. /* There are many ways this simple example parser could be improved.  Here   */
  99. /* are a few.                                                                */
  100. /*                                                                           */
  101. /* 1. The parser tables are held in a file called 'LPEXPAS.DAT'.  This name  */
  102. /*    is hard-wired in the code, but should really be made a command param-  */
  103. /*    eter called, say, 'TABLE'.  This could even be extended to allow dif-  */
  104. /*    ferent table names for different invocations of the parser.            */
  105. /*                                                                           */
  106. /*    A 'REFRESH' option would be useful too, so that if changes are made to */
  107. /*    the tables the in-store copies could be updated without having to      */
  108. /*    restart LPEX.                                                          */
  109. /*                                                                           */
  110. /* 2. Some of the 'readXxxxx()' functions are rather too simple and should   */
  111. /*    be improved.  readNumber(), for example, only accepts a string of      */
  112. /*    decimal digits, and really ought to accept real numbers (with decimal  */
  113. /*    points and/or exponents) too.                                          */
  114. /*                                                                           */
  115. /* 3. No distinction is made between the two types of comment markers ('{ }' */
  116. /*    and '(* *)', so an opening '{' can be matched to a closing '*)'.  This */
  117. /*    could be improved.                                                     */
  118. /*                                                                           */
  119. /* Note, however, that an 'emphasis parser' of this sort is only meant as an */
  120. /* aid to the programmer.  It may not be worthwhile trying to make it 'too'  */
  121. /* perfect if, as a result, its performance starts to suffer.                */
  122. /*                                                                           */
  123. /*****************************************************************************/
  124. /*                                                                           */
  125. /* The parser is now DBCS (double byte character sets) enabled.  That is, it */
  126. /* will scan the elements correctly, never confusing the second byte of a    */
  127. /* DBCS character with an SBCS character.  DBCS characters (byte pairs) are  */
  128. /* permitted in quoted strings and within comments, but not within function  */
  129. /* or variable names.  In those cases, they are higlighted in FONT_ERROR to  */
  130. /* indicate that the compiler will also have problems.                       */
  131. /*                                                                           */
  132. /*****************************************************************************/
  133.  
  134.  
  135. /* include standard C functions */
  136. #include <stdio.h>
  137. #include <string.h>
  138. #include <memory.h>
  139. #include <ctype.h>
  140. #include <stdlib.h>
  141.  
  142.  
  143. /* LPEX API (API) access functions */
  144. #include "lpexapi.h"
  145. #ifdef __UNIX__
  146. #include "aixfuncs.h"
  147. #endif
  148.  
  149. #ifdef __cplusplus
  150.    extern "C" {
  151. #endif
  152. int EditSearchPath (const char* path, const char* name,
  153.                     char* buff, unsigned long size,
  154.                     int fCurrentDir, int fEnvVar);
  155. #ifdef __cplusplus
  156.    }
  157. #endif
  158.  
  159.  
  160. /* some #defines make the code easier to read */
  161. #define TRUE  1
  162. #define FALSE 0
  163.  
  164. #define MAXTOKENLENGTH  32
  165. #define MAXTABLESIZE   100
  166.  
  167. #define FONT_NAME     'A'
  168. #define FONT_BUILTIN  'B'
  169. #define FONT_KEYWORD  'K'
  170. #define FONT_COMMENT  'C'
  171. #define FONT_ERROR    'E'
  172. #define FONT_LITERAL  'L'
  173. #define FONT_SYMBOL   'S'
  174. #define FONT_NUMBER   'N'
  175. #define FONT_LAYOUT   '_'
  176.  
  177. #define CLASSES "CLASSES space opencomment comment code function error"
  178.  
  179. #define CLASS_SPACE        0x80000000       /* the order of these bits must   */
  180. #define CLASS_OPENCOMMENT  0x40000000       /*  match that of the string      */
  181. #define CLASS_COMMENT      0x20000000       /*  above, with the MSB the first */
  182. #define CLASS_CODE         0x10000000       /*  class in the string           */
  183. #define CLASS_FUNCTION     0x08000000
  184. #define CLASS_ERROR        0x04000000
  185. #define CLASSMASK          0x03FFFFFF       /* only reset first 6 classes     */
  186.  
  187.  
  188. /* declare all our functions */
  189. int   loadAllTables (char*);
  190. int   loadTable (char**, FILE*);
  191. char* getLine (FILE*);
  192.  
  193. int   parseCurrent (void);
  194. int   parseElement (void);
  195. int   readComment (int);
  196. int   readLayout (int);
  197. int   readLiteral (int);
  198. int   readNumber (int);
  199. int   readWord (int);
  200.  
  201. int   testSymbol (int);
  202.  
  203. int   isKeyWord (char*);
  204. int   isBuiltIn (char*);
  205. int   isOpenComment (void);
  206.  
  207.  
  208. /*---------------------------------------------------------------------------*/
  209. /* Global data.                                                              */
  210. /*---------------------------------------------------------------------------*/
  211.  
  212. /* we define three large buffers, suitable for passing data to & from LPEX */
  213.  
  214. uchr* textbuf;
  215. uchr* fontbuf;
  216. uchr* modebuf;
  217.  
  218. /* a flag to indicate we have bought memory for the three buffers above */
  219.  
  220. int buffers = FALSE;
  221.  
  222. /* a flag to indicate if the keyword and symbol tables have been loaded yet */
  223.  
  224. int tablesLoaded = FALSE;
  225.  
  226. /* pointers to the data tables, and their associated sizes */
  227.  
  228. char* keywords[MAXTABLESIZE];
  229. char* builtin[MAXTABLESIZE];
  230. char* symbols[MAXTABLESIZE];
  231.  
  232. int keysize;
  233. int builtsize;
  234. int symsize;
  235.  
  236. /* there is some shared data among the parsing routines */
  237.  
  238. int InsideComment = FALSE;            /* flag to indicate we are in a comment */
  239. int Error         = FALSE;            /* flag to indicata an error detected   */
  240. int fPendOff      = FALSE;            /* TRUE if ALL and PARSER set           */
  241.  
  242. char LastWord[MAXTOKENLENGTH+1];      /* last word processed                  */
  243.                                       /*  N.B. We assume that the data tables */
  244.                                       /*  will only contain SBCS.             */
  245.  
  246.  
  247. /***************************************************************************/
  248. /* lxxquer() return pointer to query value lxquery() returns a string      */
  249. /* like "ITEM setting" so we need to point past the "ITEM " and at the     */
  250. /* setting itself.                                                         */
  251. /***************************************************************************/
  252.  
  253. char* lxxquer (char* item, char* buff)
  254. {
  255.    uchr *p;
  256.  
  257.    lxquery(item, buff);                        /* returns "ITEM value" string */
  258.    if (*(p = buff + strlen(item)) == '\0')
  259.       return p;                    /* if not set, LPEX just returns "ITEM'\0' */
  260.    else
  261.       return (p + 1);                          /* step over item name & blank */
  262. }
  263.  
  264.  
  265. /***************************************************************************/
  266. /* lxxqnum() returns as an int the result of a query                       */
  267. /***************************************************************************/
  268.  
  269. int   lxxqnum (char* item)
  270. {
  271.    char work[100];
  272.    return (atoi(lxxquer(item, work)));
  273. }
  274.  
  275.  
  276. /***************************************************************************/
  277. /* setup()  allocate space for the arrays.                                 */
  278. /***************************************************************************/
  279.  
  280. int   setup (void)
  281. {
  282.    if (!buffers) {
  283.       /* now buy some buffers... */
  284.       if ((textbuf = lxalloc(MAXLEN +1)) == NULL)
  285.          return -8;
  286.       if ((fontbuf = lxalloc(MAXLEN +1)) == NULL) {
  287.          lxfree(textbuf);
  288.          return -8;
  289.          }
  290.       if ((modebuf = lxalloc(MAXLEN +1)) == NULL) {
  291.          lxfree(textbuf);
  292.          lxfree(fontbuf);
  293.          return -8;
  294.          }
  295.       buffers = TRUE;
  296.       }
  297.    return 0;
  298. }
  299.  
  300.  
  301. /***************************************************************************/
  302. /* lxexit() This is called when the user asks for an UNLINK, or when LPEX  */
  303. /* is about to come down.                                                  */
  304. /***************************************************************************/
  305.  
  306. int   lxexit (uchr* parm)
  307. {
  308.    if (modebuf != NULL)
  309.       lxfree(modebuf);
  310.    if (textbuf != NULL)
  311.       lxfree(textbuf);
  312.    if (fontbuf != NULL)
  313.       lxfree(fontbuf);
  314.    return 0;
  315. }
  316.  
  317.  
  318. /*---------------------------------------------------------------------------*/
  319. /* This is the entry point to the program.  We examine the parameters        */
  320. /* passed to us from LPEX and act accordingly.                               */
  321. /*---------------------------------------------------------------------------*/
  322.  
  323. int   lxmain (uchr* parameters)
  324. {
  325.    char *p;
  326.    int rc;
  327.  
  328.    if ((rc = setup()) != 0)                  /* allocate space for the arrays */
  329.       return rc;
  330.    strcpy(textbuf, parameters);                          /* make a local copy */
  331.    strupr(textbuf);                    /* force parameters to upper case, and */
  332.    for (p = textbuf; *p == ' '; p++);  /*  skip over any leading blanks       */
  333.  
  334.    if (*p != '\0' &&                 /* only valid parameter, if any, is ALL: */
  335.        strncmp(p, "ALL", 3) != 0) {
  336.       sprintf(fontbuf, "PRPAS - unexpected argument %.40s", p);
  337.       lxcall("MSG", fontbuf);        /*  issue error message otherwise...     */
  338.       return -12;
  339.       }
  340.    else {
  341.       uchr buff[255];
  342.       lxquery("PARSER", buff);
  343.       fPendOff = (strlen(buff) > 7);
  344.       }
  345.  
  346.    /* first we check if the tables of key words and language symbols have    */
  347.    /* been loaded from disk yet.  If not, we try to load them.               */
  348.  
  349.    if (!tablesLoaded) {
  350.       if ((rc = loadAllTables("LPEXPAS.DAT")) != 0)
  351.          return rc;
  352.       tablesLoaded = TRUE;
  353.       }
  354.  
  355.    /* parsing may well alter the current position, so we set a mark here to  */
  356.    /* preserve it, then restore it again when we've finished.  By convention */
  357.    /* mark names containing periods are used only inside commands and macros */
  358.    /* so we can be sure that no mark called "PARSER.SAVE" already exists.    */
  359.  
  360.    lxcmd("MARK SET PARSER.SAVE");
  361.  
  362.    /* now we call the parsing routine itself.  Either for the current ele-   */
  363.    /* ment or, if the "ALL" parameter is specified, for each element in the  */
  364.    /* document.  The "ALL" option is designed to be called once from a .LXL  */
  365.    /* load macro, so also sets the classes used by the parser.               */
  366.  
  367.    if (*p == '\0')                         /* just parse the current element */
  368.       rc = parseCurrent();
  369.    else {                                        /* or do the whole document: */
  370.       lxcall("SET", CLASSES);
  371.       lxcmd("TOP");
  372.       InsideComment = FALSE;
  373.       do {
  374.          rc = parseElement();                 /* 0 or positive = all was well */
  375.          if (fPendOff)
  376.             lxcmd("SET PENDING OFF");
  377.          } while ((rc >= 0) && lxnext() == 0);
  378.       }
  379.  
  380.    /* restore the original position and return to LPEX */
  381.  
  382.    lxcmd("MARK FIND PARSER.SAVE");
  383.    lxcmd("MARK CLEAR PARSER.SAVE");
  384.  
  385.    return rc;
  386. }
  387.  
  388.  
  389. /*---------------------------------------------------------------------------*/
  390. /* These are the routines to load the data tables from disk.                 */
  391. /*---------------------------------------------------------------------------*/
  392.  
  393. /* loadAllTables() - Load all three data tables. */
  394.  
  395. int   loadAllTables (char* filename)
  396. {
  397.    char *p;
  398.    int rc;
  399.    FILE *datafile;
  400.  
  401.    /* we should look for the file containing the tables either in the  */
  402.    /* current directory or in the path held in LPEX's "LPATH" setting. */
  403.  
  404.    p = lxxquer("LPATH", textbuf);                            /* get the LPATH */
  405.  
  406.    if (EditSearchPath(p, filename, fontbuf, MAXLEN,
  407.                      TRUE  /*SEARCH_CUR_DIRECTORY*/,
  408.                      FALSE /*SEARCH_ENVIRONMENT*/)) {
  409.       sprintf(textbuf, "PRPAS - parser tables \"%.40s\" not found", filename);
  410.       lxcall("MSG", textbuf);
  411.       return -2;
  412.       }
  413.  
  414.    /* open the parser tables as a simple text file */
  415.  
  416.    if ((datafile = fopen(fontbuf, "r")) == NULL) {
  417.       sprintf(textbuf, "PRPAS - cannot open parser tables \"%.40s\"", fontbuf);
  418.       lxcall("MSG", textbuf);
  419.       return -2;
  420.       }
  421.  
  422.    /* there are three tables to be loaded, first the keywords, then */
  423.    /* the built-in functions, and lastly the language symbols.      */
  424.  
  425.    rc = 0;
  426.  
  427.    if ((keysize   = loadTable(keywords, datafile)) < 0 ||
  428.        (builtsize = loadTable(builtin,  datafile)) < 0 ||
  429.        (symsize   = loadTable(symbols,  datafile)) < 0)
  430.       rc = -3;
  431.  
  432.    /* close the parser tables */
  433.  
  434.    fclose(datafile);
  435.  
  436.    return rc;
  437. }
  438.  
  439.  
  440. /*****************************************************************************/
  441. /* loadTable() - Load the next parser table from disk.                       */
  442. /*               Returns the number of items in the table, or -1 if there is */
  443. /*               an error.                                                   */
  444. /*****************************************************************************/
  445.  
  446. int   loadTable (char** table, FILE* file)
  447. {
  448.    char *p, *q;
  449.    int len, size;
  450.  
  451.    size = 0;                                  /* no keywords in the table yet */
  452.  
  453.    do {                                       /* get the first non-comment    */
  454.       p = getLine(file);                      /*  line in the parser tables   */
  455.       } while (p != NULL && *p == '\n');
  456.  
  457.    if (feof(file) || ferror(file)) {               /* was there a read error? */
  458.       sprintf(textbuf, "PRPAS - error reading parser tables");
  459.       lxcall("MSG", textbuf);
  460.       return -1;
  461.       }
  462.  
  463.    while (p != NULL && *p != '\n') {
  464.       if (size >= MAXTABLESIZE) {
  465.          sprintf(textbuf, "PRPAS - parser table too large");
  466.          lxcall("MSG", textbuf);
  467.          return -1;
  468.          }
  469.  
  470.       q = p;                                      /* find the end of the item */
  471.       while (*q != ' ' && *q != '\n' && *q != '\0') ++q;
  472.  
  473.       len = q - p;
  474.       table[size] = lxalloc(len+1);                 /* get space for the word */
  475.       strncpy(table[size], p, len);                       /* copy the word in */
  476.       *(table[size]+len) = '\0';
  477.       strupr(table[size]);                    /* store all words in uppercase */
  478.       ++size;
  479.  
  480.       p = getLine(file);
  481.       }
  482.  
  483.    if (ferror(file)) {                             /* was there a read error? */
  484.       sprintf(textbuf, "PRPAS - error reading parser tables");
  485.       lxcall("MSG", textbuf);
  486.       return -1;
  487.       }
  488.  
  489.    return size;
  490. }
  491.  
  492.  
  493. /*****************************************************************************/
  494. /* getLine() - read a line from the parser tables and determine if it was a  */
  495. /*             comment.                                                      */
  496. /*             Returns pointer to the first non-space and non-comment        */
  497. /*             character on the line.                                        */
  498. /*****************************************************************************/
  499.  
  500. char* getLine (FILE* file)
  501. {
  502.    char *p;
  503.  
  504.    if ((p = fgets(textbuf, MAXLEN, file)) != NULL) {
  505.       if (*p == '*')                                       /* ignore comments */
  506.          *p = '\n';
  507.       while (*p == ' ') ++p;                           /* skip leading spaces */
  508.       }
  509.  
  510.    return p;
  511. }
  512.  
  513.  
  514. /*---------------------------------------------------------------------------*/
  515. /* These are the Parser Routines                                             */
  516. /*---------------------------------------------------------------------------*/
  517.  
  518. /*****************************************************************************/
  519. /* parseCurrent() - this is called to parse the current element.  It also    */
  520. /*                  handles the case of a comment continuing over several    */
  521. /*                  lines, by parsing elements before and after the current  */
  522. /*                  element if need be.                                      */
  523. /*****************************************************************************/
  524.  
  525. int   parseCurrent (void)
  526. {
  527.    int wascomment;    /* BOOLEAN */
  528.    int rc = 0;
  529.  
  530.    /* if the previous element contained an open comment, we must set the */
  531.    /* flag to indicate we are in a comment.                              */
  532.  
  533.    if (lxprev() == 0) {                      /* is there a previous element?  */
  534.       InsideComment = isOpenComment();
  535.       lxnext();                              /*  move back to the start point */
  536.       }
  537.  
  538.    /* parse the starting element, noting whether it is an */
  539.    /* open comment before we parse it.                    */
  540.  
  541.    wascomment = isOpenComment();
  542.    if ((rc =parseElement()) < 0 )
  543.       return rc;
  544.  
  545.    /* now we carry on parsing if either the current element used to be an */
  546.    /* open comment, or has now become one.  Any extra elements parsed in  */
  547.    /* this way are dropped from the trigger list to ensure that (if they  */
  548.    /* were on the list in the first place) they will not be parsed again  */
  549.    /* unnecessarily.                                                      */
  550.  
  551.    while ((wascomment || InsideComment) && lxnext() == 0) {
  552.       wascomment = isOpenComment();
  553.       if ((rc = parseElement()) < 0)
  554.          break;
  555.       if (fPendOff)
  556.          lxcmd("SET PENDING OFF");
  557.       }
  558.  
  559.    return rc;
  560. }
  561.  
  562.  
  563. /*****************************************************************************/
  564. /* parseElement() - this routine parses a single element.  It builds up a    */
  565. /*                  font string for the element and a list of possible       */
  566. /*                  classes then sets these items.                           */
  567. /*****************************************************************************/
  568.  
  569. int   parseElement (void)
  570. {
  571.    int position, length,maxpos,rc;
  572.    uchr ch, font;
  573.    unsigned long pasclass;
  574.  
  575.    /* get hold of the text for the current element, and position ourselves */
  576.    /* at the start of the element's text.                                  */
  577.  
  578.    if ((rc = lxqtext(textbuf)) < 0)      /* the result in 'textbuf' will be:  */
  579.        return rc;
  580.    position = 0;                         /* "xxxxxxxxxxxxxxxxxx...."          */
  581.    maxpos  = strlen(textbuf) - 1;        /*  ^ = index position 0             */
  582.  
  583.    /* get its class, and clear our own bits */
  584.  
  585.    lxqclass(&pasclass);
  586.    pasclass &= CLASSMASK;                    /* reset all except user classes */
  587.    pasclass |= CLASS_SPACE;                  /* no PASCAL classes defined yet */
  588.  
  589.    /* step though all the characters in the element, identifying */
  590.    /* the tokens and building a font string.                     */
  591.  
  592.    while ((ch = textbuf[position]) != '\0' ) {
  593.       if (InsideComment) {         /* (a) first check for a continued comment */
  594.          length = readComment(position);
  595.          font   = FONT_COMMENT;
  596.          pasclass |= CLASS_COMMENT;
  597.          }
  598.  
  599.       else if (isspace(ch)) {                             /* (b) layout space */
  600.          length = readLayout(position);
  601.          font   = FONT_LAYOUT;
  602.          pasclass |= CLASS_SPACE;
  603.          }
  604.  
  605.       else if (isdigit(ch)) {                                   /* (c) number */
  606.          length = readNumber(position);
  607.          font   = FONT_NUMBER;
  608.          pasclass |= CLASS_CODE;
  609.          }
  610.  
  611.       else if (ch == '\'') {           /* (d) literal string in single quotes */
  612.          length = readLiteral(position);
  613.          font   = FONT_LITERAL;
  614.          pasclass |= CLASS_CODE;
  615.          }
  616.  
  617.       else if (ch == '\"') {           /* (e) literal string in double quotes */
  618.          length = readLiteral(position);        /* (strictly, this will only  */
  619.          font   = FONT_ERROR;                   /*  cause a compiler warning, */
  620.          pasclass |= CLASS_ERROR;               /*  but it's naughty!)        */
  621.          }
  622.  
  623.       else if (ch == '{' || (ch == '(' && textbuf[position+1] == '*')) {
  624.          length = readComment(position);                      /* (f) comments */
  625.          font   = FONT_COMMENT;
  626.          pasclass |= CLASS_COMMENT;
  627.          }
  628.  
  629.       else if (ch == '!') {               /* (g) special case: comment to EOL */
  630.          length = maxpos - position + 1;
  631.          font   = FONT_COMMENT;
  632.          pasclass |= CLASS_COMMENT;
  633.          }
  634.  
  635.       else if (isalpha(ch)) {                                   /* (h) words: */
  636.          length = readWord(position);
  637.          pasclass |= CLASS_CODE;
  638.  
  639.          if (isKeyWord(LastWord)) {                              /* - keyword */
  640.             font = FONT_KEYWORD;
  641.             if (strcmp(LastWord, "FUNCTION") == 0       /* check if we have a */
  642.                 || strcmp(LastWord, "PROCEDURE") == 0   /*  function or proc- */
  643.                 || strcmp(LastWord, "PROGRAM") == 0)    /*  edure declaration */
  644.                pasclass |= CLASS_FUNCTION;
  645.             }
  646.          else if (isBuiltIn(LastWord))                 /* - built-in function */
  647.             font = FONT_BUILTIN;
  648.          else                                                       /* - name */
  649.             font = FONT_NAME;
  650.          }
  651.  
  652.       else if ((length = testSymbol(position)) > 0)     /* (i) special symbol */
  653.          font = FONT_SYMBOL;
  654.  
  655.       else {                                            /* (j) what else?!... */
  656.          length = 1;                        /* the length is always 1 in SBCS */
  657.          Error = TRUE;
  658.          }
  659.  
  660.       /* was there an error of any kind? */
  661.  
  662.       if (Error) {
  663.          font = FONT_ERROR;
  664.          pasclass |= CLASS_ERROR;
  665.          Error = FALSE;
  666.          }
  667.  
  668.       /* ensure that our internal scans worked correctly */
  669.       if (position + length > maxpos + 1) {
  670.          lxcmd("MSG PASCAL Parser error: scan past end of string");
  671.          lxcall("QUERY", "CONTENT");
  672.          sprintf(modebuf, "MSG length = %d, position = %d, maxpos = %d",
  673.                           length, position, maxpos);
  674.          lxcall("MSG", modebuf);
  675.          lxcall("QUERY", "ELEMENT");
  676.          return -11;
  677.          }
  678.  
  679.       /* now build up the font for the last thing found */
  680.       memset(fontbuf+position, font, length);
  681.       position += length;
  682.       }
  683.  
  684.    /* when we reach the end of the element, we add a terminator to the font */
  685.    /* string, and set the fonts                                             */
  686.  
  687.    fontbuf[position] = '\0';
  688.    if ((rc = lxsfont(fontbuf)) < 0)
  689.        return rc;                                 /* return on LPEX errors... */
  690.  
  691.    /* decide what classes to give the element */
  692.  
  693.    if (InsideComment)
  694.       pasclass |= CLASS_OPENCOMMENT;
  695.  
  696.    if ((pasclass & CLASS_SPACE) && (pasclass != CLASS_SPACE))
  697.       pasclass &= ~CLASS_SPACE;         /* remove the SPACE class unless it's */
  698.                                         /*  the only class for the element    */
  699.    return lxsclass(pasclass);
  700. }
  701.  
  702.  
  703. /*****************************************************************************/
  704. /* readComment() - This routine will read characters up to an end comment    */
  705. /*                 marker or the end of element.                             */
  706. /*                 It returns the length of the comment.                     */
  707. /*                                                                           */
  708. /*                 It also sets the 'InsideComment' flag if the end of the   */
  709. /*                 element is encountered before the comment ends.           */
  710. /*                                                                           */
  711. /* Pascal: Comments are ended by '}' or '*)'.  This routine accepts either.  */
  712. /* Assumes that the current position cannot be DBCS2.                        */
  713. /*****************************************************************************/
  714.  
  715. int   readComment (int pos)
  716. {
  717.    int start;
  718.    char ch;
  719.  
  720.    start = pos;
  721.  
  722.    if (!InsideComment)                       /* if this is not a continuation */
  723.       if (textbuf[pos] == '{')               /*  comment, skip over the start */
  724.          ++pos;                              /*  comment symbol               */
  725.  
  726.    for (;;) {
  727.       if ((ch = textbuf[pos]) == '\0') {
  728.          InsideComment = TRUE;
  729.          return (pos - start);
  730.          }
  731.  
  732.       if (ch == '}')                          /* if we reached a matching '}' */
  733.          break;                               /*  that's the end of comment   */
  734.  
  735.       ++pos;
  736.       if (ch == '*' && textbuf[pos] == ')')
  737.          break;
  738.       }
  739.  
  740.    InsideComment = FALSE;
  741.    return (pos + 1 - start);
  742. }
  743.  
  744.  
  745. /*****************************************************************************/
  746. /* readLayout() - This routine will read characters comprising layout space  */
  747. /*                It returns the length of the layout characters             */
  748. /*****************************************************************************/
  749.  
  750. int   readLayout (int pos)
  751. {
  752.    int start;
  753.    char ch;
  754.  
  755.    start = pos;
  756.  
  757.    while ((ch = textbuf[pos]) != '\0') {
  758.        if (isspace(ch))
  759.           ++pos;
  760.        else
  761.           break;
  762.    }
  763.    return (pos - start);
  764. }
  765.  
  766.  
  767. /*****************************************************************************/
  768. /* readLiteral() - This routine read a literal (any characters enclosed in   */
  769. /*                 single quotes).                                           */
  770. /*                 It returns the length of the literal.                     */
  771. /*                                                                           */
  772. /*                 We assume that literals must all be contained on one line */
  773. /*                 if no closing quote is found it's treated as an error.    */
  774. /*****************************************************************************/
  775.  
  776. int   readLiteral (int pos)
  777. {
  778.    int start;
  779.    char ch, quote;
  780.  
  781.    start = pos;
  782.    quote = textbuf[pos++];                        /* note the quote character */
  783.                                                   /*  and step over it        */
  784.  
  785.    while ((ch = textbuf[pos]) != '\0') {
  786.       if (ch != quote)
  787.          ++pos;                                   /* find the closing quote   */
  788.       else
  789.          return (pos +1 - start);                 /* we matched the quote     */
  790.       }
  791.                                                   /* we have reached the end  */
  792.    Error = TRUE;                                  /*  of the element before   */
  793.    return (pos - start);                          /*  the close quote         */
  794. }
  795.  
  796.  
  797. /*****************************************************************************/
  798. /* readNumber() - This reads the next number from an element.                */
  799. /*                It returns the length of the number.                       */
  800. /*                                                                           */
  801. /* Pascal: Numbers consist of Decimal digits.                                */
  802. /* Note: stops at the first byte of DBCS character including ROMA-JI numbers */
  803. /*****************************************************************************/
  804.  
  805. int   readNumber (int pos)
  806. {
  807.    int start;
  808.    char ch;
  809.  
  810.    start = pos;
  811.  
  812.    while ((ch = textbuf[pos]) != '\0') {
  813.       if (isdigit(ch))  {
  814.          ++pos;
  815.          }
  816.       else {
  817.          break;
  818.          }
  819.       }
  820.    return (pos - start);
  821. }
  822.  
  823.  
  824. /*****************************************************************************/
  825. /* readWord() - This reads the next word from an element.                    */
  826. /*              It returns the length of the word and places a copy of the   */
  827. /*              word in 'LastWord'.                                          */
  828. /*                                                                           */
  829. /* Pascal:  Words consist of letters, digits, underscores, starting with a   */
  830. /*          letter.  Therefore, stop the scan on space / punctuation / DBCS. */
  831. /*****************************************************************************/
  832.  
  833. int   readWord (int pos)
  834. {
  835.    int start, i;
  836.    char ch;
  837.  
  838.    start = pos;
  839.    i = 0;
  840.  
  841.    while ((ch = textbuf[pos]) != '\0')  {
  842.       if (!(isalnum(ch) || ch == '_') || i >= MAXTOKENLENGTH) {
  843.          break;                            /* not alphanumeric / underscore / */
  844.          }                                 /*  maximum word length reached... */
  845.       else {
  846.          LastWord[i++] = ch;                   /* valid character, so copy it */
  847.          }
  848.       ++pos;
  849.    }
  850.  
  851.    LastWord[i] = '\0';
  852.    strupr(LastWord);                         /* convert the word to uppercase */
  853.  
  854.    return (pos - start);
  855. }
  856.  
  857.  
  858. /*****************************************************************************/
  859. /* testSymbol() - See if the next token in the element is one of the special */
  860. /*                symbols listed in the table.  Choose the longest possible  */
  861. /*                special symbol.                                            */
  862. /*                It returns the length of the symbol, or zero if not a      */
  863. /*                symbol.                                                    */
  864. /*                                                                           */
  865. /* The table of language symbols is pointed to by the 'symbols' global       */
  866. /* and is set up from the parser tables the first time the parser is used.   */
  867. /* NOTE: must think more on the DBCS implications of this...                 */
  868. /*****************************************************************************/
  869.  
  870. int   testSymbol (int pos)
  871. {
  872.    char ch;
  873.    int upper, lower, ttry, c;
  874.    int length;
  875.  
  876.    /* first do a binary look-up of the first character                   */
  877.    /* Note: this doesn't necessarily put us on the first symbol to start */
  878.    /*       with this character.                                         */
  879.  
  880.    ch = textbuf[pos];
  881.    lower = 0;
  882.    upper = ttry = symsize;
  883.    c = -1;
  884.  
  885.    do {
  886.       if (c < 0)
  887.          upper = ttry-1;
  888.       else
  889.          lower = ttry+1;
  890.  
  891.       ttry = (upper+lower)/2;
  892.  
  893.       if ((c = ch - *symbols[ttry]) == 0)
  894.          break;
  895.       } while (lower < upper);
  896.  
  897.    /* if there was no match we have no symbol, otherwise look for the */
  898.    /* longest possible symbol at this point                           */
  899.  
  900.    if (c != 0)
  901.       return 0;
  902.  
  903.    /* move backwards to find the first entry in the table starting with */
  904.    /* the required character                                            */
  905.  
  906.    while (ttry > 0 && *symbols[ttry-1] == ch)
  907.       --ttry;
  908.  
  909.    /* now find the longest symbol which start with the character */
  910.  
  911.    for (;;) {
  912.       length = strlen(symbols[ttry]);
  913.       if (strncmp(textbuf+pos, symbols[ttry], length) == 0)
  914.          return length;
  915.       ++ttry;
  916.       }
  917.    return 0;       /* no match */
  918. }
  919.  
  920.  
  921. /*****************************************************************************/
  922. /* isKeyWord() - Test a word to see if it is a keyword.                      */
  923. /*               Return TRUE or FALSE as appropriate.                        */
  924. /*                                                                           */
  925. /* The table of keywords is pointed to by the global 'keywords', which is    */
  926. /* set up from the parser tables when the parser is first used.              */
  927. /* Note this is passed the string 'LastWord' which will NOT contain DBCS.    */
  928. /*****************************************************************************/
  929.  
  930. int   isKeyWord (char* word)
  931. {
  932.    int upper, lower, ttry, c;
  933.  
  934.    /* do a binary look-up in the keyword table */
  935.  
  936.    lower = 0;  upper = ttry = keysize;  c = -1;
  937.  
  938.    do {
  939.       if (c < 0)
  940.          upper = ttry - 1;
  941.       else
  942.          lower = ttry + 1;
  943.  
  944.       ttry = (upper + lower) / 2;
  945.  
  946.       if ((c = strcmp(word, keywords[ttry])) == 0)
  947.          return TRUE;
  948.  
  949.       } while (lower < upper);
  950.  
  951.    return FALSE;
  952. }
  953.  
  954.  
  955. /*****************************************************************************/
  956. /* isBuiltIn() - Test a word for a built-in function or procedure.           */
  957. /*               Return TRUE or FALSE as appropriate.                        */
  958. /*                                                                           */
  959. /* The table of built-in functions is pointed to by the global 'builtin',    */
  960. /* which is set up from the parser tables when the parser is first used.     */
  961. /*****************************************************************************/
  962.  
  963. int   isBuiltIn (char* word)
  964. {
  965.    int upper, lower, ttry, c;
  966.  
  967.    /* do a binary look-up in the builtin table */
  968.  
  969.    lower = 0;
  970.    upper = ttry = builtsize;
  971.    c = -1;
  972.  
  973.    do {
  974.       if (c < 0)
  975.          upper = ttry - 1;
  976.       else
  977.          lower = ttry + 1;
  978.  
  979.       ttry = (upper + lower) / 2;
  980.  
  981.       if ((c = strcmp(word, builtin[ttry])) == 0)
  982.          return TRUE;
  983.  
  984.       } while (lower < upper);
  985.  
  986.    return FALSE;
  987. }
  988.  
  989.  
  990. /*****************************************************************************/
  991. /* isOpenComment() - Determine whether the current element has the class     */
  992. /*                   OPENCOMMENT set.                                        */
  993. /*****************************************************************************/
  994.  
  995. int   isOpenComment (void)
  996. {
  997.    unsigned long c;
  998.  
  999.    lxqclass(&c);
  1000.    return ((c & CLASS_OPENCOMMENT) != 0L);
  1001. }
  1002.  
  1003.