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