1 /*
   2 *   $Id$
   3 *
   4 *   Copyright (c) 2013, Colomban Wendling <ban@herbesfolles.org>
   5 *
   6 *   This source code is released for free distribution under the terms of the
   7 *   GNU General Public License.
   8 *
   9 *   This module contains code for generating tags for the PHP scripting
  10 *   language.
  11 */
  12 
  13 /*
  14 *   INCLUDE FILES
  15 */
  16 #include "general.h"  /* must always come first */
  17 #include "parse.h"
  18 #include "read.h"
  19 #include "vstring.h"
  20 #include "keyword.h"
  21 #include "entry.h"
  22 #include "routines.h"
  23 #include "debug.h"
  24 
  25 #include <string.h>
  26 
  27 #define SCOPE_SEPARATOR "::"
  28 
  29 
  30 typedef enum {
  31     KEYWORD_NONE = -1,
  32     KEYWORD_abstract,
  33     KEYWORD_and,
  34     KEYWORD_as,
  35     KEYWORD_break,
  36     KEYWORD_callable,
  37     KEYWORD_case,
  38     KEYWORD_catch,
  39     KEYWORD_class,
  40     KEYWORD_clone,
  41     KEYWORD_const,
  42     KEYWORD_continue,
  43     KEYWORD_declare,
  44     KEYWORD_define,
  45     KEYWORD_default,
  46     KEYWORD_do,
  47     KEYWORD_echo,
  48     KEYWORD_else,
  49     KEYWORD_elif,
  50     KEYWORD_enddeclare,
  51     KEYWORD_endfor,
  52     KEYWORD_endforeach,
  53     KEYWORD_endif,
  54     KEYWORD_endswitch,
  55     KEYWORD_endwhile,
  56     KEYWORD_extends,
  57     KEYWORD_final,
  58     KEYWORD_finally,
  59     KEYWORD_for,
  60     KEYWORD_foreach,
  61     KEYWORD_function,
  62     KEYWORD_global,
  63     KEYWORD_goto,
  64     KEYWORD_if,
  65     KEYWORD_implements,
  66     KEYWORD_include,
  67     KEYWORD_include_once,
  68     KEYWORD_instanceof,
  69     KEYWORD_insteadof,
  70     KEYWORD_interface,
  71     KEYWORD_namespace,
  72     KEYWORD_new,
  73     KEYWORD_or,
  74     KEYWORD_print,
  75     KEYWORD_private,
  76     KEYWORD_protected,
  77     KEYWORD_public,
  78     KEYWORD_require,
  79     KEYWORD_require_once,
  80     KEYWORD_return,
  81     KEYWORD_static,
  82     KEYWORD_switch,
  83     KEYWORD_throw,
  84     KEYWORD_trait,
  85     KEYWORD_try,
  86     KEYWORD_use,
  87     KEYWORD_var,
  88     KEYWORD_while,
  89     KEYWORD_xor,
  90     KEYWORD_yield
  91 } keywordId;
  92 
  93 typedef enum {
  94     ACCESS_UNDEFINED,
  95     ACCESS_PRIVATE,
  96     ACCESS_PROTECTED,
  97     ACCESS_PUBLIC,
  98     COUNT_ACCESS
  99 } accessType;
 100 
 101 typedef enum {
 102     IMPL_UNDEFINED,
 103     IMPL_ABSTRACT,
 104     COUNT_IMPL
 105 } implType;
 106 
 107 typedef enum {
 108     K_CLASS,
 109     K_DEFINE,
 110     K_FUNCTION,
 111     K_INTERFACE,
 112     K_LOCAL_VARIABLE,
 113     K_NAMESPACE,
 114     K_TRAIT,
 115     K_VARIABLE,
 116     COUNT_KIND
 117 } phpKind;
 118 
 119 static kindOption PhpKinds[COUNT_KIND] = {
 120     { TRUE, 'c', "class",       "classes" },
 121     { TRUE, 'd', "define",      "constant definitions" },
 122     { TRUE, 'f', "function",    "functions" },
 123     { TRUE, 'i', "interface",   "interfaces" },
 124     { FALSE, 'l', "local",      "local variables" },
 125     { TRUE, 'n', "namespace",   "namespaces" },
 126     { TRUE, 't', "trait",       "traits" },
 127     { TRUE, 'v', "variable",    "variables" }
 128 };
 129 
 130 typedef struct {
 131     const char *name;
 132     keywordId id;
 133 } keywordDesc;
 134 
 135 static const keywordDesc PhpKeywordTable[] = {
 136     /* keyword          keyword ID */
 137     { "abstract",       KEYWORD_abstract        },
 138     { "and",            KEYWORD_and             },
 139     { "as",             KEYWORD_as              },
 140     { "break",          KEYWORD_break           },
 141     { "callable",       KEYWORD_callable        },
 142     { "case",           KEYWORD_case            },
 143     { "catch",          KEYWORD_catch           },
 144     { "cfunction",      KEYWORD_function        }, /* nobody knows what the hell this is, but it seems to behave much like "function" so bind it to it */
 145     { "class",          KEYWORD_class           },
 146     { "clone",          KEYWORD_clone           },
 147     { "const",          KEYWORD_const           },
 148     { "continue",       KEYWORD_continue        },
 149     { "declare",        KEYWORD_declare         },
 150     { "define",         KEYWORD_define          }, /* this isn't really a keyword but we handle it so it's easier this way */
 151     { "default",        KEYWORD_default         },
 152     { "do",             KEYWORD_do              },
 153     { "echo",           KEYWORD_echo            },
 154     { "else",           KEYWORD_else            },
 155     { "elseif",         KEYWORD_elif            },
 156     { "enddeclare",     KEYWORD_enddeclare      },
 157     { "endfor",         KEYWORD_endfor          },
 158     { "endforeach",     KEYWORD_endforeach      },
 159     { "endif",          KEYWORD_endif           },
 160     { "endswitch",      KEYWORD_endswitch       },
 161     { "endwhile",       KEYWORD_endwhile        },
 162     { "extends",        KEYWORD_extends         },
 163     { "final",          KEYWORD_final           },
 164     { "finally",        KEYWORD_finally         },
 165     { "for",            KEYWORD_for             },
 166     { "foreach",        KEYWORD_foreach         },
 167     { "function",       KEYWORD_function        },
 168     { "global",         KEYWORD_global          },
 169     { "goto",           KEYWORD_goto            },
 170     { "if",             KEYWORD_if              },
 171     { "implements",     KEYWORD_implements      },
 172     { "include",        KEYWORD_include         },
 173     { "include_once",   KEYWORD_include_once    },
 174     { "instanceof",     KEYWORD_instanceof      },
 175     { "insteadof",      KEYWORD_insteadof       },
 176     { "interface",      KEYWORD_interface       },
 177     { "namespace",      KEYWORD_namespace       },
 178     { "new",            KEYWORD_new             },
 179     { "or",             KEYWORD_or              },
 180     { "print",          KEYWORD_print           },
 181     { "private",        KEYWORD_private         },
 182     { "protected",      KEYWORD_protected       },
 183     { "public",         KEYWORD_public          },
 184     { "require",        KEYWORD_require         },
 185     { "require_once",   KEYWORD_require_once    },
 186     { "return",         KEYWORD_return          },
 187     { "static",         KEYWORD_static          },
 188     { "switch",         KEYWORD_switch          },
 189     { "throw",          KEYWORD_throw           },
 190     { "trait",          KEYWORD_trait           },
 191     { "try",            KEYWORD_try             },
 192     { "use",            KEYWORD_use             },
 193     { "var",            KEYWORD_var             },
 194     { "while",          KEYWORD_while           },
 195     { "xor",            KEYWORD_xor             },
 196     { "yield",          KEYWORD_yield           }
 197 };
 198 
 199 
 200 typedef enum eTokenType {
 201     TOKEN_UNDEFINED,
 202     TOKEN_EOF,
 203     TOKEN_CHARACTER,
 204     TOKEN_CLOSE_PAREN,
 205     TOKEN_SEMICOLON,
 206     TOKEN_COLON,
 207     TOKEN_COMMA,
 208     TOKEN_KEYWORD,
 209     TOKEN_OPEN_PAREN,
 210     TOKEN_OPERATOR,
 211     TOKEN_IDENTIFIER,
 212     TOKEN_STRING,
 213     TOKEN_PERIOD,
 214     TOKEN_OPEN_CURLY,
 215     TOKEN_CLOSE_CURLY,
 216     TOKEN_EQUAL_SIGN,
 217     TOKEN_OPEN_SQUARE,
 218     TOKEN_CLOSE_SQUARE,
 219     TOKEN_VARIABLE,
 220     TOKEN_AMPERSAND
 221 } tokenType;
 222 
 223 typedef struct {
 224     tokenType       type;
 225     keywordId       keyword;
 226     vString *       string;
 227     vString *       scope;
 228     unsigned long   lineNumber;
 229     fpos_t          filePosition;
 230     int             parentKind; /* -1 if none */
 231 } tokenInfo;
 232 
 233 static langType Lang_php;
 234 
 235 static boolean InPhp = FALSE; /* whether we are between <? ?> */
 236 
 237 /* current statement details */
 238 struct {
 239     accessType access;
 240     implType impl;
 241     vString* docblock;
 242 } CurrentStatement;
 243 
 244 /* Current namespace */
 245 vString *CurrentNamesapce;
 246 
 247 
 248 static void buildPhpKeywordHash (void)
 249 {
 250     const size_t count = sizeof (PhpKeywordTable) / sizeof (PhpKeywordTable[0]);
 251     size_t i;
 252     for (i = 0; i < count ; i++)
 253     {
 254         const keywordDesc* const p = &PhpKeywordTable[i];
 255         addKeyword (p->name, Lang_php, (int) p->id);
 256     }
 257 }
 258 
 259 static const char *accessToString (const accessType access)
 260 {
 261     static const char *const names[COUNT_ACCESS] = {
 262         "undefined",
 263         "private",
 264         "protected",
 265         "public"
 266     };
 267 
 268     Assert (access < COUNT_ACCESS);
 269 
 270     return names[access];
 271 }
 272 
 273 static const char *implToString (const implType impl)
 274 {
 275     static const char *const names[COUNT_IMPL] = {
 276         "undefined",
 277         "abstract"
 278     };
 279 
 280     Assert (impl < COUNT_IMPL);
 281 
 282     return names[impl];
 283 }
 284 
 285 static void encodeDocBlock (vString *const encoded, vString const *docblock)
 286 {
 287     int i, len;
 288     char *buff, c;
 289     buff = vStringValue (docblock);
 290     len = strlen (buff);
 291 
 292     vStringClear (encoded);
 293 
 294     for (i = 0; i < len; ++i) {
 295         c = buff[i];
 296         if (c == '\n')
 297             vStringCatS (encoded, "\\n");
 298         else if (c == '\r')
 299             vStringCatS (encoded, "\\r");
 300         else if (c == '\t')
 301             vStringCatS (encoded, "\\t");
 302         else
 303             vStringPut (encoded, c);
 304 
 305     }
 306     vStringTerminate (encoded);
 307 }
 308 
 309 static void initPhpEntry (tagEntryInfo *const e, const tokenInfo *const token,
 310                           const phpKind kind, const accessType access, vString *const docblock)
 311 {
 312     static vString *fullScope = NULL;
 313     int parentKind = -1;
 314 
 315     if (fullScope == NULL)
 316         fullScope = vStringNew ();
 317     else
 318         vStringClear (fullScope);
 319 
 320     if (vStringLength (CurrentNamesapce) > 0)
 321     {
 322         vStringCopy (fullScope, CurrentNamesapce);
 323         parentKind = K_NAMESPACE;
 324     }
 325 
 326     initTagEntry (e, vStringValue (token->string));
 327 
 328     e->lineNumber   = token->lineNumber;
 329     e->filePosition = token->filePosition;
 330     e->kindName     = PhpKinds[kind].name;
 331     e->kind         = (char) PhpKinds[kind].letter;
 332 
 333     if (access != ACCESS_UNDEFINED)
 334         e->extensionFields.access = accessToString (access);
 335     if (vStringLength (token->scope) > 0)
 336     {
 337         parentKind = token->parentKind;
 338         if (vStringLength (fullScope) > 0)
 339             vStringCatS (fullScope, SCOPE_SEPARATOR);
 340         vStringCat (fullScope, token->scope);
 341     }
 342     if (vStringLength (fullScope) > 0)
 343     {
 344         Assert (parentKind >= 0);
 345 
 346         vStringTerminate (fullScope);
 347         e->extensionFields.scope[0] = PhpKinds[parentKind].name;
 348         e->extensionFields.scope[1] = vStringValue (fullScope);
 349     }
 350     if (vStringLength (docblock) > 0)
 351     {
 352         e->extensionFields.docblock = vStringValue (docblock);
 353     }
 354 }
 355 
 356 static void makeSimplePhpTag (const tokenInfo *const token, const phpKind kind,
 357                               const accessType access)
 358 {
 359     if (PhpKinds[kind].enabled)
 360     {
 361         tagEntryInfo e;
 362 
 363         initPhpEntry (&e, token, kind, access, CurrentStatement.docblock);
 364         makeTagEntry (&e);
 365     }
 366 }
 367 
 368 static void makeNamespacePhpTag (const tokenInfo *const token, const vString *const name)
 369 {
 370     if (PhpKinds[K_NAMESPACE].enabled)
 371     {
 372         tagEntryInfo e;
 373 
 374         initTagEntry (&e, vStringValue (name));
 375 
 376         e.lineNumber    = token->lineNumber;
 377         e.filePosition  = token->filePosition;
 378         e.kindName      = PhpKinds[K_NAMESPACE].name;
 379         e.kind          = (char) PhpKinds[K_NAMESPACE].letter;
 380 
 381         makeTagEntry (&e);
 382     }
 383 }
 384 
 385 static void makeClassOrIfaceTag (const phpKind kind, const tokenInfo *const token,
 386                                  vString *const inheritance, const implType impl)
 387 {
 388     if (PhpKinds[kind].enabled)
 389     {
 390         tagEntryInfo e;
 391 
 392         initPhpEntry (&e, token, kind, ACCESS_UNDEFINED, CurrentStatement.docblock);
 393 
 394         if (impl != IMPL_UNDEFINED)
 395             e.extensionFields.implementation = implToString (impl);
 396         if (vStringLength (inheritance) > 0)
 397             e.extensionFields.inheritance = vStringValue (inheritance);
 398 
 399         makeTagEntry (&e);
 400     }
 401 }
 402 
 403 static void makeFunctionTag (const tokenInfo *const token,
 404                              const vString *const arglist,
 405                              const accessType access, const implType impl)
 406 {
 407     if (PhpKinds[K_FUNCTION].enabled)
 408     {
 409         tagEntryInfo e;
 410 
 411         initPhpEntry (&e, token, K_FUNCTION, access, CurrentStatement.docblock);
 412 
 413         if (impl != IMPL_UNDEFINED)
 414             e.extensionFields.implementation = implToString (impl);
 415         if (arglist)
 416             e.extensionFields.signature = vStringValue (arglist);
 417 
 418         makeTagEntry (&e);
 419     }
 420 }
 421 
 422 static tokenInfo *newToken (void)
 423 {
 424     tokenInfo *const token = xMalloc (1, tokenInfo);
 425 
 426     token->type         = TOKEN_UNDEFINED;
 427     token->keyword      = KEYWORD_NONE;
 428     token->string       = vStringNew ();
 429     token->scope        = vStringNew ();
 430     token->lineNumber   = getSourceLineNumber ();
 431     token->filePosition = getInputFilePosition ();
 432     token->parentKind   = -1;
 433 
 434     return token;
 435 }
 436 
 437 static void deleteToken (tokenInfo *const token)
 438 {
 439     vStringDelete (token->string);
 440     vStringDelete (token->scope);
 441     eFree (token);
 442 }
 443 
 444 static void copyToken (tokenInfo *const dest, const tokenInfo *const src,
 445                        boolean scope)
 446 {
 447     dest->lineNumber = src->lineNumber;
 448     dest->filePosition = src->filePosition;
 449     dest->type = src->type;
 450     dest->keyword = src->keyword;
 451     vStringCopy(dest->string, src->string);
 452     dest->parentKind = src->parentKind;
 453     if (scope)
 454         vStringCopy(dest->scope, src->scope);
 455 }
 456 
 457 #if 0
 458 #include <stdio.h>
 459 
 460 static const char *tokenTypeName (const tokenType type)
 461 {
 462     switch (type)
 463     {
 464         case TOKEN_UNDEFINED:       return "undefined";
 465         case TOKEN_EOF:             return "EOF";
 466         case TOKEN_CHARACTER:       return "character";
 467         case TOKEN_CLOSE_PAREN:     return "')'";
 468         case TOKEN_SEMICOLON:       return "';'";
 469         case TOKEN_COLON:           return "':'";
 470         case TOKEN_COMMA:           return "','";
 471         case TOKEN_OPEN_PAREN:      return "'('";
 472         case TOKEN_OPERATOR:        return "operator";
 473         case TOKEN_IDENTIFIER:      return "identifier";
 474         case TOKEN_KEYWORD:         return "keyword";
 475         case TOKEN_STRING:          return "string";
 476         case TOKEN_PERIOD:          return "'.'";
 477         case TOKEN_OPEN_CURLY:      return "'{'";
 478         case TOKEN_CLOSE_CURLY:     return "'}'";
 479         case TOKEN_EQUAL_SIGN:      return "'='";
 480         case TOKEN_OPEN_SQUARE:     return "'['";
 481         case TOKEN_CLOSE_SQUARE:    return "']'";
 482         case TOKEN_VARIABLE:        return "variable";
 483     }
 484     return NULL;
 485 }
 486 
 487 static void printToken (const tokenInfo *const token)
 488 {
 489     fprintf (stderr, "%p:\n\ttype:\t%s\n\tline:\t%lu\n\tscope:\t%s\n", (void *) token,
 490              tokenTypeName (token->type),
 491              token->lineNumber,
 492              vStringValue (token->scope));
 493     switch (token->type)
 494     {
 495         case TOKEN_IDENTIFIER:
 496         case TOKEN_STRING:
 497         case TOKEN_VARIABLE:
 498             fprintf (stderr, "\tcontent:\t%s\n", vStringValue (token->string));
 499             break;
 500 
 501         case TOKEN_KEYWORD:
 502         {
 503             size_t n = sizeof PhpKeywordTable / sizeof PhpKeywordTable[0];
 504             size_t i;
 505 
 506             fprintf (stderr, "\tkeyword:\t");
 507             for (i = 0; i < n; i++)
 508             {
 509                 if (PhpKeywordTable[i].id == token->keyword)
 510                 {
 511                     fprintf (stderr, "%s\n", PhpKeywordTable[i].name);
 512                     break;
 513                 }
 514             }
 515             if (i >= n)
 516                 fprintf (stderr, "(unknown)\n");
 517         }
 518 
 519         default: break;
 520     }
 521 }
 522 #endif
 523 
 524 static void addToScope (tokenInfo *const token, const vString *const extra)
 525 {
 526     if (vStringLength (token->scope) > 0)
 527         vStringCatS (token->scope, SCOPE_SEPARATOR);
 528     vStringCatS (token->scope, vStringValue (extra));
 529     vStringTerminate(token->scope);
 530 }
 531 
 532 static boolean isIdentChar (const int c)
 533 {
 534     return (isalnum (c) || c == '_' || c >= 0x80);
 535 }
 536 
 537 static int skipToCharacter (const int c)
 538 {
 539     int d;
 540     do
 541     {
 542         d = fileGetc ();
 543     } while (d != EOF  &&  d != c);
 544     return d;
 545 }
 546 
 547 static int collectToCharacter (vString *const string, const int c)
 548 {
 549     int d;
 550     do
 551     {
 552         d = fileGetc ();
 553         vStringPut (string, (char) d);
 554     } while (d != EOF  &&  d != c);
 555     vStringTerminate (string);
 556     return d;
 557 }
 558 
 559 static void parseString (vString *const string, const int delimiter)
 560 {
 561     while (TRUE)
 562     {
 563         int c = fileGetc ();
 564 
 565         if (c == '\\' && (c = fileGetc ()) != EOF)
 566             vStringPut (string, (char) c);
 567         else if (c == EOF || c == delimiter)
 568             break;
 569         else
 570             vStringPut (string, (char) c);
 571     }
 572     vStringTerminate (string);
 573 }
 574 
 575 /* reads an HereDoc or a NowDoc (the part after the <<<).
 576  *  <<<[ \t]*(ID|'ID'|"ID")
 577  *  ...
 578  *  ID;?
 579  *
 580  * note that:
 581  *  1) starting ID must be immediately followed by a newline;
 582  *  2) closing ID is the same as opening one;
 583  *  3) closing ID must be immediately followed by a newline or a semicolon
 584  *     then a newline.
 585  *
 586  * Example of a *single* valid heredoc:
 587  *  <<< FOO
 588  *  something
 589  *  something else
 590  *  FOO this is not an end
 591  *  FOO; this isn't either
 592  *  FOO; # neither this is
 593  *  FOO;
 594  *  # previous line was the end, but the semicolon wasn't required
 595  */
 596 static void parseHeredoc (vString *const string)
 597 {
 598     int c;
 599     unsigned int len;
 600     char delimiter[64]; /* arbitrary limit, but more is crazy anyway */
 601     int quote = 0;
 602 
 603     do
 604     {
 605         c = fileGetc ();
 606     }
 607     while (c == ' ' || c == '\t');
 608 
 609     if (c == '\'' || c == '"')
 610     {
 611         quote = c;
 612         c = fileGetc ();
 613     }
 614     for (len = 0; len < (sizeof delimiter / sizeof delimiter[0]) - 1; len++)
 615     {
 616         if (! isIdentChar (c))
 617             break;
 618         delimiter[len] = (char) c;
 619         c = fileGetc ();
 620     }
 621     delimiter[len] = 0;
 622 
 623     if (len == 0) /* no delimiter, give up */
 624         goto error;
 625     if (quote)
 626     {
 627         if (c != quote) /* no closing quote for quoted identifier, give up */
 628             goto error;
 629         c = fileGetc ();
 630     }
 631     if (c != '\r' && c != '\n') /* missing newline, give up */
 632         goto error;
 633 
 634     do
 635     {
 636         c = fileGetc ();
 637 
 638         if (c != '\r' && c != '\n')
 639             vStringPut (string, (char) c);
 640         else
 641         {
 642             /* new line, check for a delimiter right after */
 643             int nl = c;
 644             int extra = EOF;
 645 
 646             c = fileGetc ();
 647             for (len = 0; c != 0 && (c - delimiter[len]) == 0; len++)
 648                 c = fileGetc ();
 649 
 650             if (delimiter[len] != 0)
 651                 fileUngetc (c);
 652             else
 653             {
 654                 /* line start matched the delimiter, now check whether there
 655                  * is anything after it */
 656                 if (c == '\r' || c == '\n')
 657                 {
 658                     fileUngetc (c);
 659                     break;
 660                 }
 661                 else if (c == ';')
 662                 {
 663                     int d = fileGetc ();
 664                     if (d == '\r' || d == '\n')
 665                     {
 666                         /* put back the semicolon since it's not part of the
 667                          * string.  we can't put back the newline, but it's a
 668                          * whitespace character nobody cares about it anyway */
 669                         fileUngetc (';');
 670                         break;
 671                     }
 672                     else
 673                     {
 674                         /* put semicolon in the string and continue */
 675                         extra = ';';
 676                         fileUngetc (d);
 677                     }
 678                 }
 679             }
 680             /* if we are here it wasn't a delimiter, so put everything in the
 681              * string */
 682             vStringPut (string, (char) nl);
 683             vStringNCatS (string, delimiter, len);
 684             if (extra != EOF)
 685                 vStringPut (string, (char) extra);
 686         }
 687     }
 688     while (c != EOF);
 689 
 690     vStringTerminate (string);
 691 
 692     return;
 693 
 694 error:
 695     fileUngetc (c);
 696 }
 697 
 698 static void parseIdentifier (vString *const string, const int firstChar)
 699 {
 700     int c = firstChar;
 701     do
 702     {
 703         vStringPut (string, (char) c);
 704         c = fileGetc ();
 705     } while (isIdentChar (c));
 706     fileUngetc (c);
 707     vStringTerminate (string);
 708 }
 709 
 710 static int skipWhitespaces (int c)
 711 {
 712     while (c == '\t' || c == ' ' || c == '\n' || c == '\r')
 713         c = fileGetc ();
 714     return c;
 715 }
 716 
 717 /* <script[:white:]+language[:white:]*=[:white:]*(php|'php'|"php")[:white:]*>
 718  *
 719  * This is ugly, but the whole "<script language=php>" tag is and we can't
 720  * really do better without adding a lot of code only for this */
 721 static boolean isOpenScriptLanguagePhp (int c)
 722 {
 723     int quote = 0;
 724 
 725     /* <script[:white:]+language[:white:]*= */
 726     if (c                                   != '<' ||
 727         tolower ((c = fileGetc ()))         != 's' ||
 728         tolower ((c = fileGetc ()))         != 'c' ||
 729         tolower ((c = fileGetc ()))         != 'r' ||
 730         tolower ((c = fileGetc ()))         != 'i' ||
 731         tolower ((c = fileGetc ()))         != 'p' ||
 732         tolower ((c = fileGetc ()))         != 't' ||
 733         ((c = fileGetc ()) != '\t' &&
 734           c                != ' '  &&
 735           c                != '\n' &&
 736           c                != '\r')                ||
 737         tolower ((c = skipWhitespaces (c))) != 'l' ||
 738         tolower ((c = fileGetc ()))         != 'a' ||
 739         tolower ((c = fileGetc ()))         != 'n' ||
 740         tolower ((c = fileGetc ()))         != 'g' ||
 741         tolower ((c = fileGetc ()))         != 'u' ||
 742         tolower ((c = fileGetc ()))         != 'a' ||
 743         tolower ((c = fileGetc ()))         != 'g' ||
 744         tolower ((c = fileGetc ()))         != 'e' ||
 745         (c = skipWhitespaces (fileGetc ())) != '=')
 746         return FALSE;
 747 
 748     /* (php|'php'|"php")> */
 749     c = skipWhitespaces (fileGetc ());
 750     if (c == '"' || c == '\'')
 751     {
 752         quote = c;
 753         c = fileGetc ();
 754     }
 755     if (tolower (c)                         != 'p' ||
 756         tolower ((c = fileGetc ()))         != 'h' ||
 757         tolower ((c = fileGetc ()))         != 'p' ||
 758         (quote != 0 && (c = fileGetc ()) != quote) ||
 759         (c = skipWhitespaces (fileGetc ())) != '>')
 760         return FALSE;
 761 
 762     return TRUE;
 763 }
 764 
 765 static int findPhpStart (void)
 766 {
 767     int c;
 768     do
 769     {
 770         if ((c = fileGetc ()) == '<')
 771         {
 772             c = fileGetc ();
 773             /* <? and <?php, but not <?xml */
 774             if (c == '?')
 775             {
 776                 /* don't enter PHP mode on "<?xml", yet still support short open tags (<?) */
 777                 if (tolower ((c = fileGetc ())) != 'x' ||
 778                     tolower ((c = fileGetc ())) != 'm' ||
 779                     tolower ((c = fileGetc ())) != 'l')
 780                 {
 781                     break;
 782                 }
 783             }
 784             /* <script language="php"> */
 785             else
 786             {
 787                 fileUngetc (c);
 788                 if (isOpenScriptLanguagePhp ('<'))
 789                     break;
 790             }
 791         }
 792     }
 793     while (c != EOF);
 794 
 795     return c;
 796 }
 797 
 798 static int skipSingleComment (void)
 799 {
 800     int c;
 801     do
 802     {
 803         c = fileGetc ();
 804         if (c == '\r')
 805         {
 806             int next = fileGetc ();
 807             if (next != '\n')
 808                 fileUngetc (next);
 809             else
 810                 c = next;
 811         }
 812         /* ?> in single-line comments leaves PHP mode */
 813         else if (c == '?')
 814         {
 815             int next = fileGetc ();
 816             if (next == '>')
 817                 InPhp = FALSE;
 818             else
 819                 fileUngetc (next);
 820         }
 821     } while (InPhp && c != EOF && c != '\n' && c != '\r');
 822     return c;
 823 }
 824 
 825 static void readToken (tokenInfo *const token)
 826 {
 827     int c;
 828 
 829     token->type     = TOKEN_UNDEFINED;
 830     token->keyword  = KEYWORD_NONE;
 831     vStringClear (token->string);
 832 
 833 getNextChar:
 834 
 835     if (! InPhp)
 836     {
 837         c = findPhpStart ();
 838         if (c != EOF)
 839             InPhp = TRUE;
 840     }
 841     else
 842         c = fileGetc ();
 843 
 844     while (c == '\t' || c == ' ' || c == '\n' || c == '\r')
 845     {
 846         c = fileGetc ();
 847     }
 848 
 849     token->lineNumber   = getSourceLineNumber ();
 850     token->filePosition = getInputFilePosition ();
 851 
 852     switch (c)
 853     {
 854         case EOF: token->type = TOKEN_EOF;                  break;
 855         case '(': token->type = TOKEN_OPEN_PAREN;           break;
 856         case ')': token->type = TOKEN_CLOSE_PAREN;          break;
 857         case ';': token->type = TOKEN_SEMICOLON;            break;
 858         case ',': token->type = TOKEN_COMMA;                break;
 859         case '.': token->type = TOKEN_PERIOD;               break;
 860         case ':': token->type = TOKEN_COLON;                break;
 861         case '{': token->type = TOKEN_OPEN_CURLY;           break;
 862         case '}': token->type = TOKEN_CLOSE_CURLY;          break;
 863         case '[': token->type = TOKEN_OPEN_SQUARE;          break;
 864         case ']': token->type = TOKEN_CLOSE_SQUARE;         break;
 865         case '&': token->type = TOKEN_AMPERSAND;            break;
 866 
 867         case '=':
 868         {
 869             int d = fileGetc ();
 870             if (d == '=' || d == '>')
 871                 token->type = TOKEN_OPERATOR;
 872             else
 873             {
 874                 fileUngetc (d);
 875                 token->type = TOKEN_EQUAL_SIGN;
 876             }
 877             break;
 878         }
 879 
 880         case '\'':
 881         case '"':
 882             token->type = TOKEN_STRING;
 883             parseString (token->string, c);
 884             token->lineNumber = getSourceLineNumber ();
 885             token->filePosition = getInputFilePosition ();
 886             break;
 887 
 888         case '<':
 889         {
 890             int d = fileGetc ();
 891             if (d == '/')
 892             {
 893                 /* </script[:white:]*> */
 894                 if (tolower ((d = fileGetc ())) == 's' &&
 895                     tolower ((d = fileGetc ())) == 'c' &&
 896                     tolower ((d = fileGetc ())) == 'r' &&
 897                     tolower ((d = fileGetc ())) == 'i' &&
 898                     tolower ((d = fileGetc ())) == 'p' &&
 899                     tolower ((d = fileGetc ())) == 't' &&
 900                     (d = skipWhitespaces (fileGetc ())) == '>')
 901                 {
 902                     InPhp = FALSE;
 903                     goto getNextChar;
 904                 }
 905                 else
 906                 {
 907                     fileUngetc (d);
 908                     token->type = TOKEN_UNDEFINED;
 909                 }
 910             }
 911             else if (d == '<' && (d = fileGetc ()) == '<')
 912             {
 913                 token->type = TOKEN_STRING;
 914                 parseHeredoc (token->string);
 915             }
 916             else
 917             {
 918                 fileUngetc (d);
 919                 token->type = TOKEN_UNDEFINED;
 920             }
 921             break;
 922         }
 923 
 924         case '#': /* comment */
 925             skipSingleComment ();
 926             goto getNextChar;
 927             break;
 928 
 929         case '+':
 930         case '-':
 931         case '*':
 932         case '%':
 933         {
 934             int d = fileGetc ();
 935             if (d != '=')
 936                 fileUngetc (d);
 937             token->type = TOKEN_OPERATOR;
 938             break;
 939         }
 940 
 941         case '/': /* division or comment start */
 942         {
 943             int d = fileGetc ();
 944             if (d == '/') /* single-line comment */
 945             {
 946                 skipSingleComment ();
 947                 goto getNextChar;
 948             }
 949             else if (d == '*')
 950             {
 951                 boolean isDocBlock = FALSE;
 952                 int d2 = fileGetc ();
 953                 vString *docblock = NULL;
 954                 if (d2 == '*') {
 955                     isDocBlock = TRUE;
 956                     docblock = vStringNew ();
 957                 }
 958                 else
 959                     fileUngetc (d2);
 960 
 961 
 962                 do
 963                 {
 964                     if (isDocBlock == FALSE)
 965                         c = skipToCharacter ('*');
 966                     else
 967                         c = collectToCharacter (docblock, '*');
 968 
 969                     if (c != EOF)
 970                     {
 971                         c = fileGetc ();
 972                         if (c == '/')
 973                             break;
 974                         else
 975                             fileUngetc (c);
 976                     }
 977                 } while (c != EOF && c != '\0');
 978                 if (isDocBlock) {
 979                     vString *encoded = vStringNew ();
 980                     encodeDocBlock (encoded, docblock);
 981                     vStringDelete (docblock);
 982 
 983                     vStringClear (CurrentStatement.docblock);
 984                     vStringCatS (CurrentStatement.docblock, "/**");
 985                     vStringCat  (CurrentStatement.docblock, encoded);
 986                     vStringCatS (CurrentStatement.docblock, "/");
 987 
 988                     vStringDelete (encoded);
 989                 }
 990                 goto getNextChar;
 991             }
 992             else
 993             {
 994                 if (d != '=')
 995                     fileUngetc (d);
 996                 token->type = TOKEN_OPERATOR;
 997             }
 998             break;
 999         }
1000 
1001         case '$': /* variable start */
1002         {
1003             int d = fileGetc ();
1004             if (! isIdentChar (d))
1005             {
1006                 fileUngetc (d);
1007                 token->type = TOKEN_UNDEFINED;
1008             }
1009             else
1010             {
1011                 parseIdentifier (token->string, d);
1012                 token->type = TOKEN_VARIABLE;
1013             }
1014             break;
1015         }
1016 
1017         case '?': /* maybe the end of the PHP chunk */
1018         {
1019             int d = fileGetc ();
1020             if (d == '>')
1021             {
1022                 InPhp = FALSE;
1023                 goto getNextChar;
1024             }
1025             else
1026             {
1027                 fileUngetc (d);
1028                 token->type = TOKEN_UNDEFINED;
1029             }
1030             break;
1031         }
1032 
1033         default:
1034             if (! isIdentChar (c))
1035                 token->type = TOKEN_UNDEFINED;
1036             else
1037             {
1038                 parseIdentifier (token->string, c);
1039                 token->keyword = analyzeToken (token->string, Lang_php);
1040                 if (token->keyword == KEYWORD_NONE)
1041                     token->type = TOKEN_IDENTIFIER;
1042                 else
1043                     token->type = TOKEN_KEYWORD;
1044             }
1045             break;
1046     }
1047 
1048     if (token->type == TOKEN_SEMICOLON ||
1049         token->type == TOKEN_OPEN_CURLY ||
1050         token->type == TOKEN_CLOSE_CURLY)
1051     {
1052         /* reset current statement details on statement end, and when entering
1053          * a deeper scope.
1054          * it is a bit ugly to do this in readToken(), but it makes everything
1055          * a lot simpler. */
1056         CurrentStatement.access = ACCESS_UNDEFINED;
1057         CurrentStatement.impl = IMPL_UNDEFINED;
1058     }
1059 }
1060 
1061 static void enterScope (tokenInfo *const parentToken,
1062                         const vString *const extraScope,
1063                         const int parentKind);
1064 
1065 /* parses a class or an interface:
1066  *  class Foo {}
1067  *  class Foo extends Bar {}
1068  *  class Foo extends Bar implements iFoo, iBar {}
1069  *  interface iFoo {}
1070  *  interface iBar extends iFoo {} */
1071 static boolean parseClassOrIface (tokenInfo *const token, const phpKind kind)
1072 {
1073     boolean readNext = TRUE;
1074     implType impl = CurrentStatement.impl;
1075     tokenInfo *name;
1076     vString *inheritance = NULL;
1077 
1078     readToken (token);
1079     if (token->type != TOKEN_IDENTIFIER)
1080         return FALSE;
1081 
1082     name = newToken ();
1083     copyToken (name, token, TRUE);
1084 
1085     inheritance = vStringNew ();
1086     /* skip until the open bracket and assume every identifier (not keyword)
1087      * is an inheritance (like in "class Foo extends Bar implements iA, iB") */
1088     do
1089     {
1090         readToken (token);
1091 
1092         if (token->type == TOKEN_IDENTIFIER)
1093         {
1094             if (vStringLength (inheritance) > 0)
1095                 vStringPut (inheritance, ',');
1096             vStringCat (inheritance, token->string);
1097         }
1098     }
1099     while (token->type != TOKEN_EOF &&
1100            token->type != TOKEN_OPEN_CURLY);
1101 
1102     makeClassOrIfaceTag (kind, name, inheritance, impl);
1103 
1104     if (token->type == TOKEN_OPEN_CURLY)
1105         enterScope (token, name->string, K_CLASS);
1106     else
1107         readNext = FALSE;
1108 
1109     deleteToken (name);
1110     vStringDelete (inheritance);
1111 
1112     return readNext;
1113 }
1114 
1115 /* parses a trait:
1116  *  trait Foo {} */
1117 static boolean parseTrait (tokenInfo *const token)
1118 {
1119     boolean readNext = TRUE;
1120     tokenInfo *name;
1121 
1122     readToken (token);
1123     if (token->type != TOKEN_IDENTIFIER)
1124         return FALSE;
1125 
1126     name = newToken ();
1127     copyToken (name, token, TRUE);
1128 
1129     makeSimplePhpTag (name, K_TRAIT, ACCESS_UNDEFINED);
1130 
1131     readToken (token);
1132     if (token->type == TOKEN_OPEN_CURLY)
1133         enterScope (token, name->string, K_TRAIT);
1134     else
1135         readNext = FALSE;
1136 
1137     deleteToken (name);
1138 
1139     return readNext;
1140 }
1141 
1142 /* parse a function
1143  *
1144  * if @name is NULL, parses a normal function
1145  *  function myfunc($foo, $bar) {}
1146  *  function &myfunc($foo, $bar) {}
1147  *
1148  * if @name is not NULL, parses an anonymous function with name @name
1149  *  $foo = function($foo, $bar) {}
1150  *  $foo = function&($foo, $bar) {}
1151  *  $foo = function($foo, $bar) use ($x, &$y) {} */
1152 static boolean parseFunction (tokenInfo *const token, const tokenInfo *name)
1153 {
1154     boolean readNext = TRUE;
1155     accessType access = CurrentStatement.access;
1156     implType impl = CurrentStatement.impl;
1157     tokenInfo *nameFree = NULL;
1158 
1159     readToken (token);
1160     /* skip a possible leading ampersand (return by reference) */
1161     if (token->type == TOKEN_AMPERSAND)
1162         readToken (token);
1163 
1164     if (! name)
1165     {
1166         if (token->type != TOKEN_IDENTIFIER)
1167             return FALSE;
1168 
1169         name = nameFree = newToken ();
1170         copyToken (nameFree, token, TRUE);
1171         readToken (token);
1172     }
1173 
1174     if (token->type == TOKEN_OPEN_PAREN)
1175     {
1176         vString *arglist = vStringNew ();
1177         int depth = 1;
1178 
1179         vStringPut (arglist, '(');
1180         do
1181         {
1182             readToken (token);
1183 
1184             switch (token->type)
1185             {
1186                 case TOKEN_OPEN_PAREN:  depth++; break;
1187                 case TOKEN_CLOSE_PAREN: depth--; break;
1188                 default: break;
1189             }
1190             /* display part */
1191             switch (token->type)
1192             {
1193                 case TOKEN_AMPERSAND:       vStringPut (arglist, '&');      break;
1194                 case TOKEN_CLOSE_CURLY:     vStringPut (arglist, '}');      break;
1195                 case TOKEN_CLOSE_PAREN:     vStringPut (arglist, ')');      break;
1196                 case TOKEN_CLOSE_SQUARE:    vStringPut (arglist, ']');      break;
1197                 case TOKEN_COLON:           vStringPut (arglist, ':');      break;
1198                 case TOKEN_COMMA:           vStringCatS (arglist, ", ");    break;
1199                 case TOKEN_EQUAL_SIGN:      vStringCatS (arglist, " = ");   break;
1200                 case TOKEN_OPEN_CURLY:      vStringPut (arglist, '{');      break;
1201                 case TOKEN_OPEN_PAREN:      vStringPut (arglist, '(');      break;
1202                 case TOKEN_OPEN_SQUARE:     vStringPut (arglist, '[');      break;
1203                 case TOKEN_PERIOD:          vStringPut (arglist, '.');      break;
1204                 case TOKEN_SEMICOLON:       vStringPut (arglist, ';');      break;
1205                 case TOKEN_STRING:
1206                 {
1207                     vStringCatS (arglist, "'");
1208                     vStringCat  (arglist, token->string);
1209                     vStringCatS (arglist, "'");
1210                     break;
1211                 }
1212 
1213                 case TOKEN_IDENTIFIER:
1214                 case TOKEN_KEYWORD:
1215                 case TOKEN_VARIABLE:
1216                 {
1217                     switch (vStringLast (arglist))
1218                     {
1219                         case 0:
1220                         case ' ':
1221                         case '{':
1222                         case '(':
1223                         case '[':
1224                         case '.':
1225                             /* no need for a space between those and the identifier */
1226                             break;
1227 
1228                         default:
1229                             vStringPut (arglist, ' ');
1230                             break;
1231                     }
1232                     if (token->type == TOKEN_VARIABLE)
1233                         vStringPut (arglist, '$');
1234                     vStringCat (arglist, token->string);
1235                     break;
1236                 }
1237 
1238                 default: break;
1239             }
1240         }
1241         while (token->type != TOKEN_EOF && depth > 0);
1242 
1243         vStringTerminate (arglist);
1244 
1245         makeFunctionTag (name, arglist, access, impl);
1246         vStringDelete (arglist);
1247 
1248         readToken (token); /* normally it's an open brace or "use" keyword */
1249     }
1250 
1251     /* skip use(...) */
1252     if (token->type == TOKEN_KEYWORD && token->keyword == KEYWORD_use)
1253     {
1254         readToken (token);
1255         if (token->type == TOKEN_OPEN_PAREN)
1256         {
1257             int depth = 1;
1258 
1259             do
1260             {
1261                 readToken (token);
1262                 switch (token->type)
1263                 {
1264                     case TOKEN_OPEN_PAREN:  depth++; break;
1265                     case TOKEN_CLOSE_PAREN: depth--; break;
1266                     default: break;
1267                 }
1268             }
1269             while (token->type != TOKEN_EOF && depth > 0);
1270 
1271             readToken (token);
1272         }
1273     }
1274 
1275     if (token->type == TOKEN_OPEN_CURLY)
1276         enterScope (token, name->string, K_FUNCTION);
1277     else
1278         readNext = FALSE;
1279 
1280     if (nameFree)
1281         deleteToken (nameFree);
1282 
1283     return readNext;
1284 }
1285 
1286 /* parses declarations of the form
1287  *  const NAME = VALUE */
1288 static boolean parseConstant (tokenInfo *const token)
1289 {
1290     tokenInfo *name;
1291 
1292     readToken (token); /* skip const keyword */
1293     if (token->type != TOKEN_IDENTIFIER)
1294         return FALSE;
1295 
1296     name = newToken ();
1297     copyToken (name, token, TRUE);
1298 
1299     readToken (token);
1300     if (token->type == TOKEN_EQUAL_SIGN)
1301         makeSimplePhpTag (name, K_DEFINE, ACCESS_UNDEFINED);
1302 
1303     deleteToken (name);
1304 
1305     return token->type == TOKEN_EQUAL_SIGN;
1306 }
1307 
1308 /* parses declarations of the form
1309  *  define('NAME', 'VALUE')
1310  *  define(NAME, 'VALUE) */
1311 static boolean parseDefine (tokenInfo *const token)
1312 {
1313     int depth = 1;
1314 
1315     readToken (token); /* skip "define" identifier */
1316     if (token->type != TOKEN_OPEN_PAREN)
1317         return FALSE;
1318 
1319     readToken (token);
1320     if (token->type == TOKEN_STRING ||
1321         token->type == TOKEN_IDENTIFIER)
1322     {
1323         makeSimplePhpTag (token, K_DEFINE, ACCESS_UNDEFINED);
1324         readToken (token);
1325     }
1326 
1327     /* skip until the close parenthesis.
1328      * no need to handle nested blocks since they would be invalid
1329      * in this context anyway (the VALUE may only be a scalar, like
1330      *  42
1331      *  (42)
1332      * and alike) */
1333     while (token->type != TOKEN_EOF && depth > 0)
1334     {
1335         switch (token->type)
1336         {
1337             case TOKEN_OPEN_PAREN:  depth++; break;
1338             case TOKEN_CLOSE_PAREN: depth--; break;
1339             default: break;
1340         }
1341         readToken (token);
1342     }
1343 
1344     return FALSE;
1345 }
1346 
1347 /* parses declarations of the form
1348  *  $var = VALUE
1349  *  $var; */
1350 static boolean parseVariable (tokenInfo *const token)
1351 {
1352     tokenInfo *name;
1353     boolean readNext = TRUE;
1354     accessType access = CurrentStatement.access;
1355 
1356     name = newToken ();
1357     copyToken (name, token, TRUE);
1358 
1359     readToken (token);
1360     if (token->type == TOKEN_EQUAL_SIGN)
1361     {
1362         phpKind kind = K_VARIABLE;
1363 
1364         if (token->parentKind == K_FUNCTION)
1365             kind = K_LOCAL_VARIABLE;
1366 
1367         readToken (token);
1368         if (token->type == TOKEN_KEYWORD &&
1369             token->keyword == KEYWORD_function &&
1370             PhpKinds[kind].enabled)
1371         {
1372             if (parseFunction (token, name))
1373                 readToken (token);
1374             readNext = (boolean) (token->type == TOKEN_SEMICOLON);
1375         }
1376         else
1377         {
1378             makeSimplePhpTag (name, kind, access);
1379             readNext = FALSE;
1380         }
1381     }
1382     else if (token->type == TOKEN_SEMICOLON)
1383     {
1384         /* generate tags for variable declarations in classes
1385          *  class Foo {
1386          *      protected $foo;
1387          *  }
1388          * but don't get fooled by stuff like $foo = $bar; */
1389         if (token->parentKind == K_CLASS || token->parentKind == K_INTERFACE)
1390             makeSimplePhpTag (name, K_VARIABLE, access);
1391     }
1392     else
1393         readNext = FALSE;
1394 
1395     deleteToken (name);
1396 
1397     return readNext;
1398 }
1399 
1400 /* parses namespace declarations
1401  *  namespace Foo {}
1402  *  namespace Foo\Bar {}
1403  *  namespace Foo;
1404  *  namespace Foo\Bar;
1405  *  namespace;
1406  *  napespace {} */
1407 static boolean parseNamespace (tokenInfo *const token)
1408 {
1409     tokenInfo *nsToken = newToken ();
1410 
1411     vStringClear (CurrentNamesapce);
1412     copyToken (nsToken, token, FALSE);
1413 
1414     do
1415     {
1416         readToken (token);
1417         if (token->type == TOKEN_IDENTIFIER)
1418         {
1419             if (vStringLength (CurrentNamesapce) > 0)
1420                 vStringPut (CurrentNamesapce, '\\');
1421             vStringCat (CurrentNamesapce, token->string);
1422         }
1423     }
1424     while (token->type != TOKEN_EOF &&
1425            token->type != TOKEN_SEMICOLON &&
1426            token->type != TOKEN_OPEN_CURLY);
1427 
1428     vStringTerminate (CurrentNamesapce);
1429     if (vStringLength (CurrentNamesapce) > 0)
1430         makeNamespacePhpTag (nsToken, CurrentNamesapce);
1431 
1432     if (token->type == TOKEN_OPEN_CURLY)
1433         enterScope (token, NULL, -1);
1434 
1435     deleteToken (nsToken);
1436 
1437     return TRUE;
1438 }
1439 
1440 static void enterScope (tokenInfo *const parentToken,
1441                         const vString *const extraScope,
1442                         const int parentKind)
1443 {
1444     tokenInfo *token = newToken ();
1445     int origParentKind = parentToken->parentKind;
1446 
1447     copyToken (token, parentToken, TRUE);
1448 
1449     if (extraScope)
1450     {
1451         addToScope (token, extraScope);
1452         token->parentKind = parentKind;
1453     }
1454 
1455     readToken (token);
1456     while (token->type != TOKEN_EOF &&
1457            token->type != TOKEN_CLOSE_CURLY)
1458     {
1459         boolean readNext = TRUE;
1460 
1461         switch (token->type)
1462         {
1463             case TOKEN_OPEN_CURLY:
1464                 enterScope (token, NULL, -1);
1465                 vStringClear (CurrentStatement.docblock);
1466                 break;
1467 
1468             case TOKEN_KEYWORD:
1469                 switch (token->keyword)
1470                 {
1471                     case KEYWORD_class:
1472                         readNext = parseClassOrIface (token, K_CLASS);
1473                         vStringClear (CurrentStatement.docblock);
1474                         break;
1475                     case KEYWORD_interface:
1476                         readNext = parseClassOrIface (token, K_INTERFACE);
1477                         vStringClear (CurrentStatement.docblock);
1478                         break;
1479                     case KEYWORD_trait:
1480                         readNext = parseTrait (token);
1481                         vStringClear (CurrentStatement.docblock);
1482                         break;
1483                     case KEYWORD_function:
1484                         readNext = parseFunction (token, NULL);
1485                         vStringClear (CurrentStatement.docblock);
1486                         break;
1487                     case KEYWORD_const:
1488                         readNext = parseConstant (token);
1489                         vStringClear (CurrentStatement.docblock);
1490                         break;
1491                     case KEYWORD_define:
1492                         readNext = parseDefine (token);
1493                         vStringClear (CurrentStatement.docblock);
1494                         break;
1495 
1496                     case KEYWORD_namespace:
1497                         readNext = parseNamespace (token);
1498                         vStringClear (CurrentStatement.docblock);
1499                         break;
1500 
1501                     case KEYWORD_private:   CurrentStatement.access = ACCESS_PRIVATE;   break;
1502                     case KEYWORD_protected: CurrentStatement.access = ACCESS_PROTECTED; break;
1503                     case KEYWORD_public:    CurrentStatement.access = ACCESS_PUBLIC;    break;
1504                     case KEYWORD_var:
1505                         CurrentStatement.access = ACCESS_PUBLIC;
1506                         vStringClear (CurrentStatement.docblock);
1507                         break;
1508 
1509                     case KEYWORD_abstract:  CurrentStatement.impl = IMPL_ABSTRACT;      break;
1510 
1511                     default: break;
1512                 }
1513                 break;
1514 
1515             case TOKEN_VARIABLE:
1516                 readNext = parseVariable (token);
1517                 vStringClear (CurrentStatement.docblock);
1518                 break;
1519 
1520             default: break;
1521         }
1522 
1523         if (readNext)
1524             readToken (token);
1525     }
1526 
1527     copyToken (parentToken, token, FALSE);
1528     parentToken->parentKind = origParentKind;
1529     deleteToken (token);
1530 }
1531 
1532 static void findPhpTags (void)
1533 {
1534     tokenInfo *const token = newToken ();
1535 
1536     InPhp = FALSE;
1537     CurrentStatement.access = ACCESS_UNDEFINED;
1538     CurrentStatement.impl = IMPL_UNDEFINED;
1539     CurrentStatement.docblock = vStringNew ();
1540     CurrentNamesapce = vStringNew ();
1541 
1542     do
1543     {
1544         enterScope (token, NULL, -1);
1545     }
1546     while (token->type != TOKEN_EOF); /* keep going even with unmatched braces */
1547 
1548     vStringDelete (CurrentNamesapce);
1549     deleteToken (token);
1550 }
1551 
1552 static void initialize (const langType language)
1553 {
1554     Lang_php = language;
1555     buildPhpKeywordHash ();
1556 }
1557 
1558 extern parserDefinition* PhpParser (void)
1559 {
1560     static const char *const extensions [] = { "php", "php3", "php4", "php5", "phtml", NULL };
1561     parserDefinition* def = parserNew ("PHP");
1562     def->kinds      = PhpKinds;
1563     def->kindCount  = KIND_COUNT (PhpKinds);
1564     def->extensions = extensions;
1565     def->parser     = findPhpTags;
1566     def->initialize = initialize;
1567     return def;
1568 }
1569 
1570 /* vi:set tabstop=4 shiftwidth=4: */